Random Playlist Selector with Sonos and Home Assistant

Here’s something different for the blog. I’ve taken some inspiration from the HashiCorp RFC and am tracking my personal project that way. I find it useful.

Problem Statement

I get tired of listening to the same playlist too often. I also want to reduce the number of choices I need to make in the day. Given an input list of playlist names, pick one at random to play on home Sonos.

Ideally make the list of playlists updateable outside of code.

Example playlist names and (sources)

  • 1LIVE (TuneIn)
  • My Mix 3 (Tidal)
  • My Mix 4 (Tidal)
  • 90s Radio (Pandora)
  • Sylvan Esso Radio (Pandora)

Yes, I pay for Tidal and Pandora. Don’t judge me.

Kat had Pandora when we met and I had Spotify. Then someone recommended Tidal AND it can be purchased and integrated with my Plex Pass. I cancelled my Spotify and switched to Tidal. My Tidal review could be another blog post altogether.

Working Solution

I’m using Home Assistant to automate this.

input_select is a single item at any given time, via state, and you can change it to some other item. It only returns the current selection. In other words, it holds a state, which is one of the items in the list at a time.

Set the station_list to a number of Sonos named favorites. If you call media_player.select_source on Sonos with the name of a Sonos favorite, it plays that playlist. In Sonos, you can favorite all sorts of multi-song items from your sources.

Input Select Station List

You can have a script action that literally just rotates to the next input in the list when you run it! This is done by calling input_select: Next when the script runs. This isn’t exactly random – but guarantees a state change and guarantees you don’t select the previous station you were listening to. When you call random with only say 7 entries it just feels to me like you’re listening to the same playlist again, and again, and again. I don’t REALLY want random, I want a rotation. This is pretty effective.

I like this.

My use case for starting music is one of the following trigger conditions

  • At the end of “Alexa, Rise and Shine”
  • When saying “Alexa, Let’s Rock Out”
  • When someone comes home AND
    • The Living Room TV is not playing
    • The time is outside quiet_hours datetime object

This leads to a few triggers of the playlist each day, so should lead to some seemingly random music.

Here’s the script action for the input select: next service call.

Here’s the YAML definition of that same action.

service: input_select.select_next
data:
  cycle: true
target:
  entity_id: input_select.station_list

The next action in the script is to play the current station on Sonos from the input. We use the Jinja template syntax {{}}, which can access the input selector current state from Home Assistant. I originally had a very elaborate Jinja template until I discovered the input selector. This is much more straightforward.

service: media_player.select_source
data:
  source: "{{ states('input_select.station_list') }}"
target:
  entity_id: media_player.master_bedroom

I had to go to the docs to find that quote encoding, but it works!

It seems like entity_id would have taken a bare input_select.station_list, but I guess since the source data wasn’t a standard construct it wasn’t able to parse the input_select. So I had to invoke the template engine. I guess quoted strings are evaluated for templates? Then it found my template with {{ }}.

If you know more about how templates are and are not invoked – let me know!

This solution is working really well for me so far. I can separate my playlist updating from my scripting. The script just runs all the time, and I can use a nice GUI to update the list of playlists. The previous problem of having to update a script each time I felt like a different playlist is now gone!

Abandoned Ideas

Jinja templates come into play within the Home Assistant automation https://jinja.palletsprojects.com/en/3.1.x/

I could use input_text.stations to hold a comma separated list of stations, but this has a 255 char limit which might be a problem.

https://stackoverflow.com/questions/30515456/split-string-into-list-in-jinja

My first problem is to split the input string into a list, then choose one value in the list. It looks like Jinja can do a lot of what Python can do. Let’s try it.

Split is a method of String so we can call that. Then we choose a number between 0 and the list length. Then we get the element at that position.

{{ states("input_text.stations") }}
{% set station_list = states("input_text.stations").split(",") %}
{{ station_list | length -1 }}

{{ range(0, station_list | length -1) | random }}

{% set station_num = range(0, station_list | length -1) | random %}
{{ station_num }}

{{ station_list[station_num] | trim }}

Here’s what that example output would be.

My Mix 2, My Mix 3, Mix 2, Number, Word, Another, You, are, special, really

9 # list length 

5 # random number printed once for debugging 

8 # called random again and checking the variable

special # this would be the playlist name selected 

I had to convert that random index to station_list[station_num] and trim the leading white space from the split. I suppose I could have done that in the split earlier?

This seems too complicated, but was fun. You can see how Jinja is mostly Python.

Perhaps what I want to create instead is an input_select which appears more like a dictionary. Actually, this might just be a list.

If it was a dictionary, I could probably work with that. .values() or .items() could work out.

Yes – if input_select is a dictionary – I could use any of the supported python methods on that dictionary – just like I used the split() function above on string. No, it’s not a dictionary or a list, but it simplifies everything and I don’t need any of the above. I learned some things along the way!


Posted

in

by