Hi @MaxVRAM
my HA runs on a Protectli Vault Hardware. CPU Usage is at 5%, so i don’t think that this is the problem.
For runtime I also tried a few seconds, so this shouldn’t be the problem, too.
I have no idea actually! I’ve just updated the script to allow any value in the transition input box. Thanks for the suggestion
hi @MaxVRAM,
thanks for this awesome idea.
I tried it with two different media players (Philips TV and Sonos Speaker), but the volume immediately went up/down as I started my automation. Like if there was no duration, but I tried it with 60s and even more.
service: script.fade_volume
data:
curve: logarithmic
target_player: media_player.49pus7101_12
duration: 60
target_volume: 0.8
Any idea or someone, who has the same problems?
thanks in advance
Great spot!
I recently changed one of the lines to use abs
to abs(0)
, in an attempt to resolve an issue with null target volume values producing an error. But it turns out abs(0) doesn’t work the same way float(0) works, so I’ve reverted. I’ll find another solution and update it accordingly.
Thanks for the report. If you update your script using the recent code it should work as expected. If not, please let me know.
Cheers,
Chris.
Now it works, but I have the same problem with my Sonos as Loki81 before.
The duration is much longer than set.
I have set it to 5 seconds, but it took almost 60 seconds from 0.00 to 0.06 volume level. And then this error occurred:
Error calling SonosMediaPlayerEntity.set_volume_level on media_player.sonos: HTTPConnectionPool(host='192.168.1.105', port=1400): Max retries exceeded with url: /MediaRenderer/RenderingControl/Control (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x7f571340a0>, 'Connection to 192.168.1.105 timed out. (connect timeout=9.5)'))
So I think it’s not a problem with HA or the script. It seems the media player itself is the bottleneck.
I have testet it with my Philips TV. It took 12 seconds instead of 5 seconds.
Of course!
We like our music a bit louder while we cook. When we then sit down to the table and activate Dinner mode all the lights in the house dim down to a nice level and now the music also has a nice fade down in volume. This is way nicer than the abrupt change of volume we used to have.
hello, I followed your instructions from the first post but I am getting the following error:
service I am calling from automation:
service: script.fade_volume
data:
curve: linear
target_player: media_player.minis
duration: 10
target_volume: 0.62
script:
alias: Fade the volume of a media player
mode: restart
fields:
target_player:
name: Target media player
description: Target media player of volume fade.
required: true
example: media_player.lounge_sonos
selector:
entity:
domain: media_player
target_volume:
name: Target volume
description: Volume the media play will be at the end of the fade duration.
required: true
default: 0.5
example: '0.5'
selector:
number:
max: 1
min: 0
step: 0.01
mode: slider
duration:
name: Fade duration
description: Length of time in seconds the fade should take.
required: true
default: 5
example: '5'
selector:
number:
min: 1
max: 10000
mode: box
unit_of_measurement: s
curve:
name: Fade curve algorithm
description: Shape of the fade curve to apply.
required: true
default: logarithmic
example: logarithmic
selector:
select:
options:
- logarithmic
- bezier
- linear
variables:
steps_per_second: 10
total_steps: '{{ (steps_per_second * duration) | int(0) }}'
start_volume: '{{ state_attr(target_player, ''volume_level'') | float(0) }}'
start_diff: '{{ (target_volume - start_volume) | float(0) }}'
sequence:
- repeat:
while:
- condition: template
value_template: '{{ repeat.index < total_steps }}'
- condition: template
value_template: >-
{{ ((state_attr(target_player, 'volume_level') - target_volume) |
abs(0)) > 0.001 }}
sequence:
- service: media_player.volume_set
data_template:
entity_id: '{{ target_player }}'
volume_level: >
{% set t = repeat.index / total_steps %} {% if curve ==
'logarithmic' %}
{{ (start_volume + (t / (1 + (1 - t))) * start_diff) | float(0) }}
{% elif curve == 'bezier' %}
{{ (start_volume + (t * t * (3 - 2 * t)) * start_diff) | float(0) }}
{% else %}
{{ (start_volume + t * start_diff) | float(0) }}
{% endif %}
- delay:
hours: 0
minutes: 0
seconds: 0
milliseconds: 100
- service: media_player.volume_set
data_template:
entity_id: '{{ target_player }}'
volume_level: '{{ target_volume }}'
icon: mdi:tune-vertical-variant
I am trying to use your script with google home speakers. Am I missing something or doing it wrong in any step?
The volume is also dropping immediately on a HomePod mini speaker pair.
I think it worked initially, until I added a media_player.pause
step after it in my test automation. Then it dropped the volume but didn’t stop the playback. I disabled the pause
step and nowI just get an immediate volume drop. I have set the fade duration to 5s
.
same issue on my google home speaker
This is just what I was looking for.
With this script and if I can find/make the perfect sunrise simulation with mij hue lights it will make the perfect alarm clock.
Iam no testing it on mij HomePod mini, I let a song being played of my Roon Core for 20 min, but when the time is reached 17min I get the following error:
Stopped because of unknown reason “null” 3 maart 2023 om 16:33:43 (runtime: 1026.64 seconds)
does anybody else has this error and know how to fix it?
I really hope we can get a core feature in Home Assistant where it just works as a variable in the media_player service calls. Just like lights with transition
variable, this would be great if media_players supported transition
.
Would it be possible to have the automation stop itself it it detects a volume change that it did not call, this would be great for easily stoping the automation in the morning.
First of all. Great automation! Thanks for sharing it!
I had an issue were HA crashed (and rebooted) when using it with google cast to fade in over 15 min. I never found the exact error message or issue.
I did however solve it by decreasing the update rate.
I changed:
- delay: '00:00:00.1
to - delay: '00:00:00.5'
and
steps_per_second: 10
to steps_per_second: 2
It now work flawlessly for my use case!
this would be amazing
For those struggling with timing, we got sub-second resolution in automations & scripts in 0.113 but there still seem to be limitations. I’ve been testing on a 2018 Mac Mini (3 GHz 6-Core Intel Core i5)- which should have plenty of horsepower for this- and the fastest I can adjust Spotify’s volume is about every 350 msec. Maybe it’s an internet thing with the Spotify integration phoning home, I don’t know.
That’s still three changes per second though so if your performance is the same as mine and you get the delta right it should be smooth enough.
@fmon For whatever it’s worth, I shared a light-fader script that I had written the other day, and in that script I haven’t had any trouble updating lights as often as every ≈110 ms or so.
Perhaps the latency might be on Spotify’s side?
Seems likely. Thanks Ashley!
I’ve modified this script into a version that takes an input number helper to store the players current volume to save or restore the volume automatically. Script will only restore volume when turned down to prevent automations from fighting the user for volume changes and will only save volume when above zero.
alias: Media player save/restore w/fade
sequence:
- choose:
- conditions:
- condition: template
value_template: "{{ state_attr( target_player, 'volume_level')|float(0) > 0 }}"
- condition: template
value_template: "{{ mode == 'Save' }}"
sequence:
- service: input_number.set_value
data:
value: "{{ state_attr( target_player, 'volume_level')|float(0)}}"
target:
entity_id: "{{ save_helper }}"
- repeat:
while:
- condition: template
value_template: "{{ repeat.index < total_steps }}"
- condition: template
value_template: >-
{{ (((state_attr(target_player, "volume_level") |float(0)) -
target_volume) | abs) > 0.001 }}
sequence:
- service: media_player.volume_set
data_template:
entity_id: "{{ target_player }}"
volume_level: >
{% set t = repeat.index / total_steps %} {% if curve ==
'logarithmic' %}
{{ (start_volume + (t / (1 + (1 - t))) * start_diff) | float(0) }}
{% elif curve == 'bezier' %}
{{ (start_volume + (t * t * (3 - 2 * t)) * start_diff) | float(0) }}
{% else %}
{{ (start_volume + t * start_diff) | float(0) }}
{% endif %}
- delay: "00:00:00.1"
- service: media_player.volume_set
data_template:
entity_id: "{{ target_player }}"
volume_level: "{{ target_volume }}"
enabled: true
mode: parallel
fields:
target_player:
name: Target media player
description: Target media player of volume fade.
required: true
example: media_player.example_speaker
selector:
entity:
domain: media_player
save_helper:
name: Input Number to save volume
description: Must be range 0 to 1, step size 0.01.
required: true
example: input_number.example_speaker_volume
selector:
entity:
filter:
domain: input_number
duration:
name: Fade duration
description: Length of time in seconds the fade should take.
required: true
example: "2"
selector:
number:
mode: box
min: 0
max: 100000
unit_of_measurement: s
curve:
name: Fade curve algorithm
description: Shape of the fade curve to apply.
required: true
example: logarithmic
selector:
select:
options:
- logarithmic
- bezier
- linear
mode:
name: Fade Direction
description: Save or Restore volume
required: true
example: Save
selector:
select:
options:
- Save
- Restore
variables:
steps_per_second: 10
target_volume: >-
{% if( mode == "Save" ) %}
0
{% elif( mode == "Restore" and ( ( state_attr( target_player,
'volume_level')|float(0)) < 0.01 )) %}
{{ states( save_helper ) }}
{% else %}
{{ state_attr( target_player, 'volume_level')|float(0) }}
{% endif %}
total_steps: "{{ (steps_per_second * duration) | int(0) }}"
start_volume: "{{ state_attr(target_player, 'volume_level') | float(0) }}"
start_diff: "{{ (target_volume - start_volume) | float(0) }}"
icon: mdi:tune-vertical
max: 10
I took the liberty to rewrite the script and base it on the time passed during the fade.
- no more deviation based on how ‘fast’ the script runs - fades take as long as specified by caller.
- duration now uses the duration selector type (
HH:MM:SS
format) - the time between fade steps can be adjusted from
10 - 2000ms
to accomodate media players that do not like commands in rapid succession - Possibility to abort a fade by emitting a custom event
media_player_fade_volume_abort
with the matchingtarget_player
in the additional data.
The Updated Script
media_player_fade_volume:
alias: Fade the volume of a media player
mode: restart
fields:
target_player:
name: Target media player
description: Target media player of volume fade.
required: true
example: media_player.lounge_sonos
selector:
entity:
domain: media_player
target_volume:
name: Target volume
description: Volume the media play will be at the end of the fade duration.
required: true
default: 0.5
example: "0.5"
selector:
number:
max: 1
min: 0
step: 0.01
mode: slider
duration:
name: Fade duration
description: Length of time the fade should take.
required: true
default:
hours: 00
minutes: 00
seconds: 05
selector:
duration:
curve:
name: Fade curve algorithm
description: Shape of the fade curve to apply.
required: true
default: logarithmic
example: logarithmic
selector:
select:
options:
- logarithmic
- bezier
- linear
fade_step_timeout:
name: "Time between volume steps in milliseconds. [Script Tuning]"
description: >-
Smaller value -> smoother fade steps (but could cause problems with some players interpreting this as bad behaviour).
Bigger value -> less events to media player (which could be needed in case media player is overwhelmed by rapid commands).
500ms (2 fade steps per second) is a good compromise - no audible steps and most players should handle the load fine.
required: true
default: 500
example: "500"
selector:
number:
max: 2000
min: 10
step: 10
mode: slider
unit_of_measurement: ms
variables:
start_volume: "{{ state_attr(target_player, 'volume_level') | float(0) }}"
start_timestamp: "{{ as_timestamp(now()) }}"
fade_volume_diff: "{{ (target_volume - start_volume) | float(0) }}"
fade_duration: "{{ duration.hours * 3600 + duration.minutes * 60 + duration.seconds }}"
fade_duration_cutoff: "{{ fade_duration - 0.2 }}"
sequence:
- alias: "Set the media player volume in incremental steps for the fade duration."
repeat:
sequence:
- alias: "Set next volume step. Value based on time progress of fade, start/end volume and algorithm."
service: media_player.volume_set
data_template:
entity_id: "{{ target_player }}"
volume_level: >-
{%- set relative_fade_pos = (as_timestamp(now()) - start_timestamp) / fade_duration %}
{%- if curve == 'logarithmic' %}
{{ (start_volume + (relative_fade_pos / (1 + (1 - relative_fade_pos))) * fade_volume_diff) | float(0) }}
{%- elif curve == 'bezier' %}
{{ (start_volume + (relative_fade_pos * relative_fade_pos * (3 - 2 * relative_fade_pos)) * fade_volume_diff) | float(0) }}
{%- else %}
{{ (start_volume + relative_fade_pos * fade_volume_diff) | float(0) }}
{%- endif %}
- alias: "Fade can be aborted by sending an event - wait for event before we continue with next fade step."
wait_for_trigger:
- alias: "'media_player_fade_volume_abort' event with matching target_player entity"
platform: event
event_type: "media_player_fade_volume_abort"
event_data:
target_player: "{{ target_player }}"
timeout:
milliseconds: "{{ fade_step_timeout }}"
- alias: "If abort event was received we stop script execution right away. Volume remains at current value."
if:
- alias: "Received the 'media_player_fade_volume_abort' event with target_player of current script instance."
condition: template
value_template: "{{ wait.trigger != none }}"
then:
- stop: "Script aborted by 'media_player_fade_volume_abort' event."
until:
- alias: "Time passed in fade is close to desired duration."
condition: template
value_template: "{{ (as_timestamp(now()) - start_timestamp) >= fade_duration_cutoff }}"
- alias: "Ensure media player is set to target volume after fade finished"
service: media_player.volume_set
data_template:
entity_id: "{{ target_player }}"
volume_level: "{{ target_volume }}"
icon: mdi:tune-vertical
Example Event to Abort Ongoing Fade
event_type: media_player_fade_volume_abort
data:
target_player: media_player.lounge_sonos
Media player has to be the same as the script is currently fading the volume on.
Parallelism
Currently the script restarts if started again which aborts the ongoing fade and starts the new one.
It would be cool to be able to run this script in parallel for multiple media players.
Currently I only have one player so no need for this use-case but it was the first thing I thought about when I had this finished
For this it would be needed to check
- change mode to parallel
- check before script starts fading the volume if script is already running for same media player
- script stops itself if it is already running for the same media player
Amazing, exactly what I was looking for, Game Changer, no more abrupt off/on
Thank you so much