[SOLVED] Music fade out script with variables from lovelace

EDIT: Solved: solution below:
Solution was to use the custom lovelace card: config-template-card. This card provides templating for about any variable used in the card. Without this templating, my variables would not be passed on to the script in the right format (or not at all I guess).

I’m creating a script that fades out the volume of a player based on a number of variables passed on by a lovelace card (music fades out slowly as the person falls asleep). This script can then be re-used anywhere else where you want to fade out (you can customize the duration and player in the lovelace card.

Input number (slider) where you set the fade-out duration:

fadeout_music_bedroom_1:
  name: Music off after
  min: 15
  max: 120
  step: 15
  icon: mdi:timer-sand
  mode: slider

A simple Lovelace card for testing (button to run script + the slider):

type: 'custom:config-template-card'
entities:
  - input_number.fadeout_music_bedroom_1
  - media_player.music_kitchen
card:
  type: vertical-stack
  cards:
    - type: button
      icon: 'mdi:play'
      tap_action:
        action: call-service
        service: script.media_player_fade_out
        service_data:
          media_player: media_player.music_kitchen
          vol_step: >-
            ${parseFloat(this.hass.states['media_player.music_kitchen'].attributes['volume_level'])
            / 5 }
          fade_mins: >-
            ${parseFloat(this.hass.states['input_number.fadeout_music_bedroom_1'].state)
            * 60 / 20 }
    - type: entities
      entities:
        - input_number.fadeout_music_bedroom_1

And then I use my 3 variables (media_player, vol_step & fade_mins) in the script to fade out the volume:

#gradually fade out music using passed on variables
media_player_fade_out:
  alias: Gradually reduce volume of media player
  sequence:
  - repeat:
      while: "{{ state_attr(media_player, 'volume_level') > 0 }}"
      sequence:
        - service: media_player.volume_set
          data_template:
            entity_id: '{{media_player}}'
            volume_level: >
              {% set n = state_attr(media_player, 'volume_level') %}
              {{ n - vol_step }}
        - delay: 
            seconds: '{{fade_mins}}'
  - service: media_player.turn_off
    data_template:
      entity_id: '{{media_player}}'

The problem is that when I run this, the fade-out happens really fast and for some reason doesn’t even stop when the volume is 0 (in developer tools I see it just keeps lowering below 0).

However, when I use an absolute value in my Lovelace card (i.e. fade_mins: 15), everything works perfect. So there must be something with the value of the slider-state that is being passed on. If I put this in Developer Tools/Template:
</s> <s>{{ states('input_number.gotosleep_tijd_music_slaapkamer_1') | int }}</s> <s>
I do get the value 15 as result. But that’s not the number my script is receiving.

I’m not sure this will work either but try this:

- delay: 
    minutes: >
      {{ fade_mins }}

You’re actually passing the template to the script. Lovelace does not evaluate templates. Not without this anyway:

Thanks for the suggestion Tom, however I still have the same behavior with this adjustment. Fade-out happens really fast. As if the actually value being passed on is 0.

I also tried changing my slider with a minimum value of 0 so that the selected value (i.e. 15) is not the smallest possible one. But also same behavior.

Because you are not passing a value. Lovelace has no idea what to do with this:

fade_mins: {{ (states(''input_number.gotosleep_mins_music_bedroom_1'') | int) }}

Even if it did it should be:

fade_mins: "{{ (states('input_number.gotosleep_mins_music_bedroom_1') | int) }}"

Don’t bother trying that it won’t work either.

Use a JavaScript template to pass the value using the link I posted.

Or hard code the input number in a template in the script.

media_player_fade_out:
  alias: Slowly fade out music volume
  sequence:
  - repeat:
      while: "{{ state_attr(media_player, 'volume_level') > 0 }}"
      sequence:
        - service: media_player.volume_set
          data_template:
            entity_id: '{{media_player}}'
            volume_level: >
              {% set n = state_attr(media_player, 'volume_level') %}
              {{ n - (state_attr(media_player, 'volume_level')/20) }}
        - delay: 
            minutes: "{{ (states('input_number.gotosleep_mins_music_bedroom_1') | int) }}"

I’m trying to understand the config-template-card but that doesn’t seem to be so obvious. I’ll have to start with some very simple examples.
Hardcoding is exactly what I want to avoid with this. The idea is that I can re-use the script for any device or room where a fade-out is required.

So you are going to have more than one fade time input number?

Not really, but it will be reused by multiple lovelace cards. For example I have a “GoToSleep” card for every bedroom where you can configure the fade-out time for both lights and music.
So Bedroom 1, 2, 3… all have their own card with hardcoded lights and music players, but I don’t want to have a seperate script for each one just to fade out the music. By passing on these parameters, I can re-use the same script (also for other use-cases).
At least that was the idea. Still haven’t gotten nowhere with the template card.

I also tried using a custom music sensor with multiple attributes (like fade_in / fade_out) but also there I was hitting a wall.

Then you don’t need to pass the input_number as a value, just use it like this in your script:

        - delay: 
            minutes: "{{ (states('input_number.gotosleep_mins_music_bedroom_1') | int) }}"

But then I’m stuck again with a script for every room as the input number is dedicated to bedroom 1 but in the other bedrooms another input_number will be used. If I would use the same input_number in each room (or wherever I want to use this script), I would not be able to customize the fade-out time.

The fade-out time is calculated as follows:

  • I’m looking at the current volume (i.e. 0.3) and divide this by 20 (I’ll always fade out in 20 steps, regardless of the duration).

  • Then I reduce the volume with that calculated volume step (i.e. 0.3 / 20 = 0.15) and I need to repeat that until the volume hits 0 but of course each step with a delay which is variable based on the total fade-out duration

  • For test purposes, I’m now just passing the total duration and using that in delay (which isn’t working now), but in the final step, I will pass on the variable as: Total_duration*60/nr_of_steps (i.e. 20 min * 60 / 20 <= this would result in a 60 seconds delay between each volume reduction).

… Or am I so overthinking this? :slight_smile:

You just said you were only going to have one fade out time.

So just use the one input number in your script.

But if the script runs for bedroom 1 and the person in bedroom 1 wants a fade-out time of 60 mins. And another person in bedroom 2 wants a fade-out time of 20 mins. And they both run at the same time… wouldn’t that create conflict between both instances? The first one to run would suddenly be adjusted with a new delay based on the fade-out time of the second run.