How can i make automations with transition while the device doesnt support it?

Thanks @pmcenaney, it worked for my dimmers after several changes.
I changed the transition field into an integer with unit seconds, instead of “HH:MM:SS”.
And I used the repeat.index for the while loop in a simpler way.

fade_light:
  description: "Fades lights to a desired level over a specified transition period."
  fields:
    light:
      name: Light
      description: Entity_id of light.
      selector:
        entity:
          domain: light
      example: light.kitchen
    end_pct:
      name: End brightness Level
      description: Integer value from 0 to 100 representing the desired final brightness level.
      selector:
        number:
          min: 0
          max: 100
          step: 1
          mode: slider
      default: 50
      example: "50"
    transition:
      name: Transition Time
      description: Transition time for fading in seconds.
      selector:
        number:
          min: 0
          max: 1800
          step: 1
          mode: slider
      default: 10
      example: "10"
  mode: parallel
  sequence:
    - variables:
        start_pct: "{{ ((state_attr(light, 'brightness') | int(0))/255*100) | round(0, default=0) }}"
        end_pct: "{{ end_pct | int(0) | round(0, default=0) }}"
        delay_msec: >-
          {{ ([100, (((transition / (end_pct - start_pct)) | abs) | round(3, default=0) * 1000) | int(0)]|sort)[1] }}
        sign: "{{ 1 if start_pct < end_pct else -1 }}"
        n: "{{ 1000 * transition / delay_msec | int(0) }}"
    - repeat:
        while:
          - condition: template
            value_template: >-
              {{  repeat.index <= n }}
        sequence:
          - delay:
              milliseconds: "{{ delay_msec }}"
          - service: light.turn_on
            target:
              entity_id: "{{ light }}"
            data:
              brightness_pct: "{{ ([0, (start_pct + (repeat.index * sign)), 100]|sort)[1] }}"

The script sends a light.turn_on command with a new brightness level every delay_msec milliseconds. The minimum delay is st to 100, but I worry a bit if such small delay works well, because then the controller sends 10 commands per second. Maybe increase the minimum delay to 500 milliseconds?

For others who are watching this: this seems a good solution for applying transitions to lights or dimmers which do not support transitions.

Add the script above to scripts.yaml, assuming that your have script: !include scripts.yaml in your configuration.yaml.
Then call the script for example in the following way:

    - service: script.fade_light
      data:
        light: light.dimmer_living_room
        end_pct: 40
        transition: 10


The script above can also be used to fade out the light to a brightness level of 0. A zero level does not mean that the light is off, so after it has reached zero then you need to turn the light off. But… as a result, this will leave the light or dimmer in a state with brightness = 0. So, if you next turn on the light again, it will turn on with a brightness level of 0! This problem has been mentioned in other discussions.

A solution which is not great but works good enough for me is to remember the original brightness level, and quickly set that original brightness before turning the light off. This is done in the following script fade_out_light which uses the above script fade_light:

#########################
# Fade out light
fade_out_light:
  description: "Fades out lights, and remember the initial brightness."
  fields:
    light:
      name: Light
      description: Entity_id of light.
      selector:
        entity:
          domain: light
      example: light.kitchen
    transition:
      name: Transition Time
      description: Transition time for fading in seconds.
      selector:
        number:
          min: 0
          max: 1800
          step: 1
          mode: slider
      default: 10
      example: "10"
  mode: single
  sequence:
    - variables:
        initial_brightness: "{{ state_attr(light, 'brightness') | int(0) }}"
    - service: script.fade_light
      data:
        light: "{{ light }}"
        end_pct: 0
        transition: "{{ transition }}"
    - service: light.turn_on
      target:
        entity_id: "{{ light }}"
      data:
        brightness: "{{ initial_brightness }}"
    - service: light.turn_off
      target:
        entity_id: "{{ light }}"
2 Likes

@robert2s @pmcenaney @Danny2100 @123

We have a winner!

My wife just complimented the morning light transition. Much better than the lights slamming on in the AM.

Thank you for the Fade ON solution. I tested the Fade Off, which works, but won’t go over well in bedrooms. I can work around or figure something out.

1 Like

Huge thank you for this! I have a WiFi dimmer plug that I bought to automate the lights in snake enclosures, and after much trial and error I determined that it does not support transitions and thought I was SOL. Your script to the rescue! Snakes now have a realistic sunrise/sunset routine, and since my dimmer plug (also) doesn’t support a minimum brightness of 0, I just call light.turnoff in a separate automation 1 minute after the fade finishes and the lights turn off. Happens in reverse for “sunrise”. Not only does it look way cooler in their enclosures to have the lights gradually fade up, but they like it a lot better and they now come out and bask in expectation of the “sunrise”.

Thanks for that fade_light script.

For anyone else in my situation, I can confirm that it works just fine, to fade a cheap tuya 0-10v wireless dimmer so now it works as if it did transitions :slight_smile:

I have modified @robert2s and added rgb / color temp support

rgb

transition_rgb:
  description: Transition lights to a desired level over a specified transition period.
  fields:
    entity_id:
      name: Light
      description: Entity_id of light.
      selector:
        entity:
          domain: light
      example: light.kitchen
    brightness:
      name: Brightness Level
      description: Integer value from 0 to 255 representing the desired final brightness
        level.
      selector:
        number:
          min: 0
          max: 255
          step: 1
          mode: slider
      default: 255
      example: '255'
    rgb_color:
      name: End rgb color
      description: List with rgb values
    transition:
      name: Transition Time
      description: Transition time for fading in seconds.
      selector:
        number:
          min: 0
          max: 3600
          step: 1
          mode: slider
      default: 0
      example: '0'
  mode: parallel
  sequence:
  - variables:
      started: false
      delay_min: 1000
      delay_defaut: '{{ transition * 1000 }}'
      rgb_color_default:
      - 0
      - 0
      - 0
      brightness_start: '{{ 0 if state_attr(entity_id, ''brightness'') == None else
        state_attr(entity_id, ''brightness'')}}'
      brightness_end: '{{ brightness | int(0) | round(0) }}'
      brightness_sign: '{{ 1 if brightness_start < brightness_end else -1 }}'
      brightness_steps: '{{ (brightness_start - brightness_end) | abs }}'
      brightness_delay: '{{ delay_defaut if brightness_steps == 0 else [(1000 * transition
        / brightness_steps), delay_min] | max }}'
      rgb_color_start: '{{ rgb_color_default if state_attr(entity_id, ''rgb_color'')
        == None else state_attr(entity_id, ''rgb_color'')}}'
      rgb_color_end: '{{ rgb_color }}'
      r_sign: '{{ 1 if rgb_color_start[0] < rgb_color_end[0] else -1 }}'
      r_steps: '{{ (rgb_color_start[0] - rgb_color_end[0]) | abs }}'
      r_delay: '{{ delay_defaut if r_steps == 0 else ([(1000 * transition / r_steps),
        delay_min] | max) }}'
      g_sign: '{{ 1 if rgb_color_start[1] < rgb_color_end[1] else -1 }}'
      g_steps: '{{ (rgb_color_start[1] - rgb_color_end[1]) | abs }}'
      g_delay: '{{ delay_defaut if g_steps == 0 else ([(1000 * transition / g_steps),
        delay_min] | max) }}'
      b_sign: '{{ 1 if rgb_color_start[2] < rgb_color_end[2] else -1 }}'
      b_steps: '{{ (rgb_color_start[2] - rgb_color_end[2]) | abs }}'
      b_delay: '{{ delay_defaut if b_steps == 0 else ([(1000 * transition / b_steps),
        delay_min] | max) }}'
      delay: '{{ [brightness_delay, r_delay, g_delay, b_delay ] | min }}'
      delay_steps: '{{ 1000 * transition / delay_min }}'
      steps: '{{ [([brightness_steps, r_steps, g_steps, b_steps] | max), delay_steps]
        | min }}'
  - repeat:
      while:
      - condition: template
        value_template: '{{ repeat.index <= steps }}'
      - condition: template
        value_template: '{{ is_state(entity_id, "on") or not started}}'
      sequence:
      - if:
          - condition: template
            value_template: 'brightness_start + repeat.index * brightness_sign * brightness_steps
            / steps | round(0) > 0'
        then:
          - variables:
              started: true
      - delay:
          milliseconds: '{{ delay }}'
      - condition: template
        value_template: '{{ is_state(entity_id, "on") or not started }}'
      - service: light.turn_on
        target:
          entity_id: '{{ entity_id }}'
        data:
          brightness: '{{ brightness_start + repeat.index * brightness_sign * brightness_steps
            / steps | round(0) }}'
          rgb_color: '{{[ rgb_color_start[0] + repeat.index * r_sign * r_steps / steps
            | round(0), rgb_color_start[1] + repeat.index * g_sign * g_steps / steps
            | round(0), rgb_color_start[2] + repeat.index * b_sign * b_steps / steps
            | round(0), ]}}'

color_temp

transition_color_temp:
  description: Transition lights to a desired level over a specified transition period.
  fields:
    entity_id:
      name: Light
      description: Entity_id of light.
      selector:
        entity:
          domain: light
      example: light.kitchen
    brightness:
      name: Brightness Level
      description: Integer value from 0 to 255 representing the desired final brightness
        level.
      selector:
        number:
          min: 0
          max: 255
          step: 1
          mode: slider
      default: 255
      example: '255'
    color_temp:
      name: End color temp
      description: Int with color temp
    transition:
      name: Transition Time
      description: Transition time for fading in seconds.
      selector:
        number:
          min: 0
          max: 3600
          step: 1
          mode: slider
      default: 0
      example: '0'
  mode: parallel
  sequence:
  - variables:
      started: false
      delay_min: 1000
      delay_defaut: '{{ transition * 1000 }}'

      brightness_start: '{{ 0 if state_attr(entity_id, ''brightness'') == None else
        state_attr(entity_id, ''brightness'')}}'
      brightness_end: '{{ brightness | int(0) | round(0) }}'
      brightness_sign: '{{ 1 if brightness_start < brightness_end else -1 }}'
      brightness_steps: '{{ (brightness_start - brightness_end) | abs }}'
      brightness_delay: '{{ delay_defaut if brightness_steps == 0 else [(1000 * transition
        / brightness_steps), delay_min] | max }}'

      color_temp_start: '{{ color_temp if state_attr(entity_id, ''color_temp'') == None else
        state_attr(entity_id, ''color_temp'')}}'
      color_temp_end: '{{ color_temp | int(0) | round(0) }}'
      color_temp_sign: '{{ 1 if color_temp_start < color_temp_end else -1 }}'
      color_temp_steps: '{{ (color_temp_start - color_temp_end) | abs }}'
      color_temp_delay: '{{ delay_defaut if color_temp_steps == 0 else [(1000 * transition
        / color_temp_steps), delay_min] | max }}'

      delay: '{{ [brightness_delay, color_temp_delay ] | min }}'
      delay_steps: '{{ 1000 * transition / delay_min }}'
      steps: '{{ [([brightness_steps, color_temp_steps] | max), delay_steps]
        | min }}'
  - repeat:
      while:
      - condition: template
        value_template: '{{ repeat.index <= steps }}'
      - condition: template
        value_template: '{{ is_state(entity_id, "on") or not started}}'
      sequence:
      - if:
          - condition: template
            value_template: 'brightness_start + repeat.index * brightness_sign * brightness_steps
            / steps | round(0) > 0'
        then:
          - variables:
              started: true
      - delay:
          milliseconds: '{{ delay }}'
      - condition: template
        value_template: '{{ is_state(entity_id, "on") or not started }}'
      - service: light.turn_on
        target:
          entity_id: '{{ entity_id }}'
        data:
          brightness: '{{ brightness_start + repeat.index * brightness_sign * brightness_steps
            / steps | round(0) }}'
          color_temp: '{{ color_temp_start + repeat.index * color_temp_sign * color_temp_steps
            / steps | round(0) }}'
3 Likes

For anyone who’s lights stop at a certain %. I solved the problem by adding a tolerance to the condition that kills the script if brightness doesn’t match. Instead of (b | round(0, default=0) == start_pct + ((repeat.index - 1) * sign)) }} you can do

(b | round(0, default=0) - (start_pct + ((repeat.index - 1) * sign))) | abs <2 }}

1 Like

can you please post the whole script? I’m not sure which version you are modifying.

I am using the latest one by @CooleRnax and have two issues so far:

  • The fade sometimes stops at random value
  • the fade is very slow. I set 5 seconds, it takes like 15.

And also I would like the option to terminate for cases when I need to suddenly switch to max etc.

light_fader:
    description: 'Fades lights to a desired level over a specified transition period.'
    fields:
      light:
        name: Light
        description: Entity_id of light.
        selector:
          entity:
            domain: light
        example: light.kitchen
      end_pct:
        name: End brightness Level
        description: Integer value from 0 to 100 representing the desired final brightness level.
        selector:
          number:
            min: 0
            max: 100
            step: 1
            mode: slider
        default: 50
        example: 80
      transition:
        name: Transition Time
        description: Transition time for fading indicated in HH:MM:SS format (Hours:Minutes:Seconds).
        selector:
          time:
        default: '00:10:00'
        example: '00:05:30'
    mode: parallel
    sequence:
    - variables:
        start_pct: "{{ ((state_attr(light, 'brightness') | int(0))/255*100) | round(0, default=0) }}"
        end_pct: "{{ end_pct | int(0) }}"
        delay_msec: >- 
          {% set t = strptime(transition, '%H:%M:%S').time() %}
          {{ ([100, ((((t.hour*3600 + t.minute*60 + t.second) / (end_pct - start_pct)) | abs) | round(3, default=0) * 1000) | int(0)]|sort)[1] }}
        sign: "{{ 1 if start_pct < end_pct else -1 }}"
    - repeat:
        while:
        - condition: template
          value_template: >-
            {% set b = state_attr(light, 'brightness') %}
            {% set b = (b/255*100)|round(0, default=0) if b != none else b %}
            {{  repeat.index <= 102 and
                ( (sign ==  1 and (b == none or b < end_pct)) or
                  (sign == -1 and (b != none or b|int(0) > end_pct)) )
                and (b | round(0, default=0) - (start_pct + ((repeat.index - 1) * sign))) | abs <2 }}
        sequence:
        - service: light.turn_on
          target:
            entity_id: "{{ light }}"
          data:
            brightness_pct: "{{ ([0, (start_pct + (repeat.index * sign)), 100]|sort)[1] }}"
        - delay: 
            milliseconds: "{{ delay_msec }}"

I’m not sure how to modify @CooleRnax script, maybe
value_template: 'brightness_start + repeat.index * brightness_sign * brightness_steps / steps | round(0) > 0' is the line that checks if brightness is correct.

And also I would like the option to terminate for cases when I need to suddenly switch to max etc.

I think that is already build it.

This can happen when connection to light is slow and unstable.
You can try increasing delay_min

transition_color_temp:
  description: Transition lights to a desired level over a specified transition period.
  fields:
    entity_id:
      name: Light
      description: Entity_id of light.
      selector:
        entity:
          domain: light
      example: light.kitchen
    brightness:
      name: Brightness Level
      description: Integer value from 0 to 255 representing the desired final brightness
        level.
      selector:
        number:
          min: 0
          max: 255
          step: 1
          mode: slider
      default: 255
      example: '255'
    color_temp:
      name: End color temp
      description: Int with color temp
    transition:
      name: Transition Time
      description: Transition time for fading in seconds.
      selector:
        number:
          min: 0
          max: 3600
          step: 1
          mode: slider
      default: 0
      example: '0'
  mode: parallel
  sequence:
  - variables:
      started: false
      delay_min: 1000
      delay_defaut: '{{ transition * 1000 }}'

      brightness_start: '{{ 0 if state_attr(entity_id, ''brightness'') == None else
        state_attr(entity_id, ''brightness'')}}'
      brightness_end: '{{ brightness | int(0) | round(0) }}'
      brightness_sign: '{{ 1 if brightness_start < brightness_end else -1 }}'
      brightness_steps: '{{ (brightness_start - brightness_end) | abs }}'
      brightness_delay: '{{ delay_defaut if brightness_steps == 0 else [(1000 * transition
        / brightness_steps), delay_min] | max }}'

      color_temp_start: '{{ color_temp if state_attr(entity_id, ''color_temp'') == None else
        state_attr(entity_id, ''color_temp'')}}'
      color_temp_end: '{{ color_temp | int(0) | round(0) }}'
      color_temp_sign: '{{ 1 if color_temp_start < color_temp_end else -1 }}'
      color_temp_steps: '{{ (color_temp_start - color_temp_end) | abs }}'
      color_temp_delay: '{{ delay_defaut if color_temp_steps == 0 else [(1000 * transition
        / color_temp_steps), delay_min] | max }}'

      delay: '{{ [brightness_delay, color_temp_delay ] | min }}'
      delay_steps: '{{ 1000 * transition / delay_min }}'
      steps: '{{ [([brightness_steps, color_temp_steps] | max), delay_steps]
        | min }}'
  - repeat:
      while:
      - condition: template
        value_template: '{{ repeat.index <= steps }}'
      - condition: template
        value_template: '{{ is_state(entity_id, "on") or not started}}'
      sequence:
      - if:
          - condition: template
            value_template: 'brightness_start + repeat.index * brightness_sign * brightness_steps
            / steps | round(0) > 0'
        then:
          - variables:
              started: true
      - delay:
          milliseconds: '{{ delay }}'
      - condition: template
        value_template: '{{ is_state( , "on") or not started }}'
      - service: light.turn_on
        target:
          entity_id: '{{ entity_id }}'
        data:
          brightness: '{{ brightness_start + repeat.index * brightness_sign * brightness_steps
            / steps | round(0) }}'
          color_temp: '{{ color_temp_start + repeat.index * color_temp_sign * color_temp_steps
            / steps | round(0) }}'

@CooleRnax
I’ve got the above working nicely. I had used a ‘simpler’ variant from a couple of replies above that worked without the color temperature setting. However my understanding was, if I read this script correctly, that this one should halt when the status of the lamp is changed. For example, if when the script is running, I turn the light OFF, it should stop the transition/script. I can’t for the life of me get this to work.

Does anyone have the magic answer?

For reference, this is the earlier version that worked for without the color temperature.

fade_light:
  description: "Fades lights to a desired level over a specified transition period."
  fields:
    light:
      name: Light
      description: Entity_id of light.
      selector:
        entity:
          domain: light
      example: light.kitchen
    end_pct:
      name: End brightness Level
      description: Integer value from 0 to 100 representing the desired final brightness level.
      selector:
        number:
          min: 0
          max: 100
          step: 1
          mode: slider
      default: 50
      example: "50"
    transition:
      name: Transition Time
      description: Transition time for fading in seconds.
      selector:
        number:
          min: 0
          max: 1800
          step: 1
          mode: slider
      default: 10
      example: "10"
  mode: parallel
  sequence:
    - variables:
        start_pct: "{{ ((state_attr(light, 'brightness') | int(0))/255*100) | round(0, default=0) }}"
        end_pct: "{{ end_pct | int(0) | round(0, default=0) }}"
        delay_msec: >-
          {{ ([100, (((transition / (end_pct - start_pct)) | abs) | round(3, default=0) * 1000) | int(0)]|sort)[1] }}
        sign: "{{ 1 if start_pct < end_pct else -1 }}"
        n: "{{ 1000 * transition / delay_msec | int(0) }}"
    - repeat:
        while:
          - condition: template
            value_template: >-
              {{  repeat.index <= n }}
        sequence:
          - delay:
              milliseconds: "{{ delay_msec }}"
          - service: light.turn_on
            target:
              entity_id: "{{ light }}"
            data:
              brightness_pct: "{{ ([0, (start_pct + (repeat.index * sign)), 100]|sort)[1] }}"

The scope of the variable started is limited to the then section.

        then:
          - variables:
              started: true

started is undefined outside of then.

Reference: Scope of variables

Hm, yes it looks like you’re correct. So started will then ALWAYS be defined as false (as set in the beginning) and thus, even though is_state(entity_id, "on") should now catch it, and stop the script. It won’t because started == true … Yes this now is starting to make sense.

Now to figure out a way to fix it. Would it be too easy to say that the point where started becomes true should be outside the IF/THEN scope and perhaps come right after the delay in the sequence?

Due to the scoping rule, If you define started prior to the repeat you can’t change its value within the repeat (or within the if-then). Your script effectively created two variables with the same name (started) where the first one’s value is independent of the second one.

I see, makes sense. I guess I’ll need to look closer at the other versions people have made. I believe they did some other checks to have the script halt if one were to override the light manually.

My experience/knowledge on the HA-way/templating stuff is still fresh and limited. Back to the ol’ drawing board we go!

That feature was included in the original script I posted a long time ago … and it was based on work created by mrsnyds. It checks if the light’s brightness differs from what it had set it to (implying someone changed it manually).

I see, and yes I did try that version. It never worked for me. I believe it might be due to the way the transition time input is shown on my end. Using a time input instead of a number of seconds.

Did I miss one of the comments where this was addressed?

Edit: Also tried running this from the developer tools via yaml instead of ui, just turns the light on only. Nothing else.

I believe someone fixed it by changing the transition time to an integer value. However, I haven’t followed all subsequent changes to it. My only interest (a long time ago) was to simplify mrsnyds original work by employing a repeat (newly introduced at the time); I don’t actually use this fader script (because all my lighting natively supports transition).

Cool, I think I’ve dialed it down to where I’m pretty sure this is the key part I’m looking for:

while:
        - condition: template
          value_template: >-
            {% set b = state_attr(light, 'brightness') %}
            {% set b = (b/255*100)|round(0, default=0) if b != none else b %}
            {{  repeat.index <= 102 and
                ( (sign ==  1 and (b == none or b < end_pct)) or
                  (sign == -1 and (b != none or b|int(0) > end_pct)) ) and 
                (b | round(0, default=0) == start_pct + ((repeat.index - 1) * sign)) }}

Let me see how i can incorporate this in the version that DOES work for me ! Cheers for the help so far @123

It’s this part here:

(b | round(0, default=0) == start_pct + ((repeat.index - 1) * sign))

It’s checking if light’s current brightness value is equal to the previous value that it had set. If it isn’t then (someone/something external to the automation had changed it) the Template Condition reports false and the while stops looping.

1 Like

Cool, thanks! But that also implies I’ll need these two lines, where b gets defined, right?

{% set b = state_attr(light, 'brightness') %}
{% set b = (b/255*100)|round(0, default=0) if b != none else b %}