Un piacevole accompagnamento musicale, mentre svolge i suoi compiti al servizio dell’efficienza domestica e della nostra comodità. Con questo package e la sua dashboard doniamo un po' di brio alla nostra casa con l’aiuto del nostro hub domotico.
Cosa vogliamo fare
Come abbiamo detto tante volte, una casa smart si prende cura di noi essendo in grado di riconoscere ed adattarsi silenziosamente alle nostre esigenze. Ma il benessere fisico passa senza dubbio alcuno anche da quello spirituale. In questo post andiamo perciò a realizzare un centro musicale interamente web, un player Radio e Spotify.
Nota Alcune delle funzionalità descritte richiedono un abbonamento Spotify Premium. Tuttavia il package è in grado di funzionare ugualmente anche con il profilo free, con le medesime limitazioni riscontrabili su qualsiasi altro dispositivo connesso a Spotify.
Cosa ci serve
Supponiamo di avere in casa almeno uno speaker integrato in Home Assistant: nel mio caso son o presenti tre dispsitivi Google Home Mini, indirizzabili singolarmente oppure in due gruppi (tutti o solo due di questi) definiti mediante app Google Home.
I componenti di Home Assitant necessari per quest progetto sono:
- Integrazioni native:
- HACS (integrazioni/frontend):
Rimandiamo alle guide ufficiali ed ai numerosi articoli presenti in rete su come predisporre tutto quanto necessario ad utilizzare i componenti sopra elencati.
La logica
Lo sforzo maggiore nella realizzazione di questo Music Player è stato quello profuso per la convivenza delle due anime Web-Radio e Spotify. Nella stessa dashboard infatti troviamo tutti i controlli necessari per gestire:
- gli speaker, nel nostro caso 3 dispositivi e 2 gruppi
- le sorgenti audio, ovvero stream radio e/o playlist Spotify
- la riproduzione (anche contemporanea) dalle sorgenti
L’interazione utente è realizzata mediante alcuni input_select
con i quali implementiamo le selezioni di sorgente, speaker e playlist. La variazione di queste selezioni fa scattare le automazioni per l’avvio/spegnimento della riproduzione e per il controllo degli speaker.
Una serie di sensori template ci informa in ogni momento sullo stato del player, consentendoci di condizionare l’esecuzione delle automazioni e al contempo di visualizzarne le informazioni sulla dashboard.
Gli stream audio gestibili contemporaneamente sono due: interno, sia Radio sia Spotify, mediante media_player
definiti in Home Assistant, ed esterno, solo Spotify, se è attiva la riproduzione su altri dispositivi non integrati.
Nota Ricordiamo che, a differenza di Alexa e di altri dispositivi come i Sonos, l’integrazione degli speaker Google in Spotify non consente di definire in modo perenne i relativi device, ma è necessario avviare la riproduzione su uno di questi per averne visibilità. Per fare questo utilizziamo il componente Spotcast. In alternativa si può lanciare la riproduzione con un comando vocale.
Integrazioni native Google Cast e Spotify
Come anticipato, l’integrazione di questi, e degli altri, componenti necessari al progetto non è spiegata in questo post, essendo semplice e ben documentata in rete.
Quello che ci interessa qui è che, ad integrazione ultimata, se tutto saràa andato correttamente, avremo a disposizione in Home Assistant le entità:
media_player.tuoi_device
: una entità per ciascuno dei dispotivi e dei gruppi Google Cast disponibilimedia_player.spotify
: collegato alla riproduzione su account Spotify
Scriviamo i file yaml
Vista la numerosità delle funzionalità che vogliamo implementare, il codice da scrivere è abbastanza corposo, ma con un attento utilizzo dei template
(a tutti i livelli) abbiamo provato a semplificarlo. Inoltre, per comodità, lo organizziamo su più file .yaml
:
- cartella
config/packages
:- pkg_music_player: file principale del package
- cartella
config/automations
:- auto_music_player: file con le automazioni base
- auto_music_player_playlist: file con le automazioni per selezione stream
- cartella
config/lovelace-views
:- view_music_player: implementazione interfaccia lovelace
- popup_music_player: file con il contenuto dei popup
Package
Come sempre, il nostro package avrà in testa una sezione obbligatoria di personaizzazione, nella quale andiamo a dare un nome amichevole ed una icona alle entità di seguito definite, ad esempio:
homeassistant:
customize:
sensor.audio_speaker_attivo:
friendly_name: Speaker Attivo
icon: mdi:audio-cast
# altre entità
...
Come abbiamo anticipato, le interazioni utente sono realizzate mediante dei selettori, che andiamo a definire di seguito, in modo abbastanza auto-esplicativo:
# selettore sorgente audio
radio_spotcast_source:
name: Radio_Spotify Source
options:
- Radio
- Spotify
- Spento
initial: Spento
icon: mdi:apple-airplay
# selettore player
radio_player:
name: Radio Player
options:
- Tutta_casa
- Diffusione
- Camera
...
- Spento
initial: Spento
icon: mdi:speaker
# selettore playlist spotify
radio_spotcast_playlist:
name: Spotify Playlist
options:
- Daily Mix 1
- Daily Mix 2
...
- Spento
icon: mdi:spotify
# selettore stream radio
radio_station:
name: Radio Station
options:
- Radio Deejay
- Rai Radio 1
- Rai Radio 2
...
- Spento
initial: Spento
icon: mdi:radio
Sensori
Procediamo con i primi sensori template
, che, rispettivamente, ci dicono in ogni momento quale speaker è in riproduzione (ovvero il suo stato assume valore playing
) e quale è lo speaker selezionato dall’utente in interfaccia:
sensor:
- platform: template
sensors:
audio_speaker_attivo:
value_template: >
{% if (states.media_player.diffusione.state=="playing") %}
{{'media_player.diffusione'}}
{% elif (states.media_player.mini_cucina.state=="playing") %}
{{'media_player.mini_cucina'}}
{% elif (states.media_player.mini_soggiorno.state=="playing") %}
{{'media_player.mini_soggiorno'}}
{% elif (states.media_player.mini_camera.state=="playing") %}
{{'media_player.mini_camera'}}
{% else %}
{{'media_player.tutta_casa'}}
{% endif %}
audio_speaker_selezionato:
value_template: >
{% if (states.input_select.radio_player.state in ["Spento", "Tutta casa"]) %}
{{'media_player.tutta_casa'}}
{% elif (states.input_select.radio_player.state=="Diffusione") %}
{{'media_player.diffusione'}}
{% else %}
media_player.mini_{{states.input_select.radio_player.state.lower()}}
{% endif %}
Ugualmente, sempre mediante la stessa piattaforma template
, implementiamo dei sensori binari (on/off) che ci diano informazioni sullo stato della riproduzione:
audio_speaker_selezionato_muted
indica se lo speaker selezionato è in mutoaudio_riproduzione
indica se c’è una riproduzione in corso sui dispositivi integratiaudio_spotify
indica se il componente Spotify è attivoaudio_spotify_esterno
indica se l’account spotify è in uso per una riproduzione su dispotivi esterniaudio_radio
indica se la radio è in riproduzione
binary_sensor:
- platform: template
sensors:
audio_speaker_selezionato_muted:
value_template: >
{{ states[states.sensor.audio_speaker_selezionato.state].attributes.is_volume_muted }}
audio_riproduzione:
value_template: >
{{ states.media_player.mini_cucina.state=="playing"
or states.media_player.mini_soggiorno.state=="playing"
or states.media_player.mini_camera.state=="playing" }}
audio_spotify:
value_template: >
{{ states.media_player.spotify.state!="idle" }}
audio_spotify_esterno:
value_template: >
{{ states.media_player.spotify.state!="idle" and
'Mini Cucina' not in states.media_player.spotify.attributes.source_list and
'Mini Soggiorno' not in states.media_player.spotify.attributes.source_list and
'Mini Camera' not in states.media_player.spotify.attributes.source_list }}
audio_radio:
value_template: >
{{ states.binary_sensor.audio_riproduzione.state=="on" and states.binary_sensor.audio_spotify.state=="off" }}
Automazioni
Nella sezione automation:
implementiamo tutti gli automatismi alla base del funzionamento del Music Player. Viste però le dimensioni importanti, per favorirne una gestione più comoda ed ordinata, possiamo tenere le automazioni in un file separato, da posizionare insieme a tutte le automazioni in una cartella automations
creata all’occorrenza. Queste automazioni saranno poi richiamate nel configuration.yaml
in questo modo:
# configuration.yaml
automation: !include_dir_merge_list automations
Nel file auto_music_player_playlist.yaml
implementiamo la selezione delle sorgenti audio (Radio/Spotify) e dei contenuti (stazione/playlist), che, mediante le rispettive automazioni, avviano la riproduzione:
# Selezione playlist Spotify
- alias: Music_Spotify_Riproduci_playlist
trigger:
- platform: state
entity_id: input_select.radio_spotcast_playlist
condition:
condition: and
conditions:
- condition: template
value_template: >
{{ is_state("input_select.radio_spotcast_source", "Spotify") }}
- condition: template
value_template: >
{{ not is_state("input_select.radio_player", "Spento") }}
- condition: template
value_template: >
{{ not is_state("input_select.radio_spotcast_playlist", "Spento") }}
action:
- delay: "00:00:05" #attesa effettiva attivazione speaker
- service: spotcast.start
data_template:
entity_id: >
{{ states['sensor.audio_speaker_selezionato'].state }}
uri: >
{%-if is_state("input_select.radio_spotcast_playlist", "Daily Mix 1") %} spotify:playlist:playlistuniqueidentifier
{%-elif is_state("input_select.radio_spotcast_playlist", "Daily Mix 2") %} spotify:playlist:playlistuniqueidentifier
...
{% endif %}
random_song: true
shuffle: true
# Selezione stream web radio
- alias: Music_Radio_Riproduci_stazione
trigger:
- platform: state
entity_id: input_select.radio_station
condition:
condition: and
conditions:
- condition: template
value_template: >
{{ is_state("input_select.radio_spotcast_source", "Radio") }}
- condition: template
value_template: >
{{ not is_state("input_select.radio_player", "Spento") }}
- condition: template
value_template: >
{{ not is_state("input_select.radio_spotcast_playlist", "Spento") }}
action:
- service: media_player.play_media
data_template:
entity_id: >
{{ states.sensor.audio_speaker_selezionato.state }}
media_content_id: >
{%-if is_state("input_select.radio_station", "Radio Deejay") %} http://radiodeejay-lh.akamaihd.net/i/RadioDeejay_Live_1@189857/master.m3u8
{%-elif is_state("input_select.radio_station", "Rai Radio 1") %} http://icestreaming.rai.it/1.mp3
{%-elif is_state("input_select.radio_station", "Rai Radio 2") %} http://icestreaming.rai.it/2.mp3
...
{% else %}
''
{% endif %}
media_content_type: "music"
Procediamo nel file auto_music_player.yaml
ad implementiamo tutte le altre funzioni basen necessarie. Durante la riproduzione è possibile cambiare il player utilizzato senza perdere la selezione della sorgente e del contenuto che si sta ascoltando:
- alias: Music_Cambia_player
trigger:
- platform: state
entity_id: input_select.radio_player
condition: >
{{ not is_state("input_select.radio_player", "Spento") }}
action:
- service: media_player.turn_off
data:
entity_id:
- media_player.tutta_casa
- media_player.diffusione
...
- delay: '00:00:01'
- service: media_player.turn_on
data_template:
entity_id: >
{{ states.sensor.audio_speaker_selezionato.state }}
- delay: '00:00:01'
- service: automation.trigger
data_template:
entity_id: >
{%-if is_state("input_select.radio_spotcast_source", "Spotify") %}
automation.music_spotify_riproduci_playlist
{% else %}
automation.music_radio_riproduci_stazione
{% endif %}
A seguire è implementato lo spegnimento da interfaccia:
- alias: Music_Spegni_Musica
trigger:
- platform: state
entity_id: input_select.radio_spotcast_source
to: "Spento"
condition: >
{{ not is_state("input_select.radio_player", "Spento") }}
action:
- service: media_player.turn_off
data:
entity_id:
- media_player.tutta_casa
...
- service: input_select.select_option
data:
entity_id: input_select.radio_player
option: 'Spento'
- service: input_select.select_option
data:
entity_id: input_select.radio_station
option: 'Spento'
- service: input_select.select_option
data:
entity_id: input_select.radio_spotcast_playlist
option: 'Spento'
Ed infine, una comoda funzione di spegnimento automatico nel caso di interruzione della riproduzione dall’esterno (ad esempio via comando vocale):
- alias: Music_Riproduzione_finita
trigger:
- platform: state
entity_id: binary_sensor.audio_riproduzione
to: "off"
for: "00:01:00" #considera spento trascorso questo tempo
action:
- service: input_select.select_option
data:
entity_id: input_select.radio_spotcast_source
option: "Spento"
Interfaccia: il layout della plancia
Passiamo adesso a disegnare l’interfaccia del nostro Music Player che sarà una pagina del nostro Home Assistant. Per impostare il layout della dashboard usiamo la custom:layout-card
, con la quale organizziamo le card in tre aree, lasciando una riga in alto come spazio vuoto regolabile all’occorrenza:
- header con tutti i pulsanti di controllo
- panel1 per il controllo dei device disponibili
- panel2 con le info sulla riproduzione in corso
- type: custom:layout-card
layout_type: custom:grid-layout
layout:
grid-template-columns: auto 450px 400px auto
grid-template-rows: 5px 80px 400px
grid-gap: 5px
grid-template-areas: |
". . . ."
"header header header header"
". panel1 panel2 ."
cards:
# Pulsanti di controllo
- type: horizontal-stack
view_layout:
grid-area: header
cards:
...
# Controllo singoli players
- type: custom:stack-in-card
mode: vertical
view_layout:
grid-area: panel1
cards:
...
# Pannello riproduzione
- type: custom:stack-in-card
view_layout:
grid-area: panel2
mode: vertical
cards:
...
Interfaccia: i pulsanti di controllo
Sfuttiamo il meccanismo dei template a corredo della custom:button-card
per modellare i pulsanti della nostra interfaccia, in modo da riutilizzare la maggior parte di codice possibile, andando a snellire la definizione della pagina stessa. Partiamo con i template dei pulsanti di selezione delle stazioni radio, con o senza logo:
# Pulsante stazione radio solo nome
radio_preset:
variables:
var_name: "Radio Name"
var_option: ""
entity: input_select.radio_station
name: '[[[ return variables.var_name ]]]'
show_name: true
show_icon: false
tap_action:
action: call-service
service: input_select.select_option
service_data:
entity_id: input_select.radio_station
option: '[[[ return variables.var_option=="" ? variables.var_name : variables.var_option ]]]'
styles:
card: [height: 40px]
name:
- font-size: 10pt
- color: >
[[[
if (entity.state==variables.var_name || entity.state==variables.var_option)
return 'lime'
return ''
]]]
# Pulsante stazione radio con aggiunta del logo
radio_preset_logo:
template: ['radio_preset']
layout: icon_name
entity_picture: >
[[[
var logo=''
var selected=(variables.var_option=='' ? variables.var_name : variables.var_option)
if (selected=="Radio Deejay")
logo='RadioDeejay.png'
else if (selected=="Rai Radio 1")
logo='RaiRadio1.png'
else if (selected=="Rai Radio 2")
logo='RaiRadio2.png'
...
else
logo='RadioWWW.png'
return '/local/radio/60X37/' + logo
]]]
Continuiamo con il template per il generico pulsante, che ne definisce l’estetica di base:
radio_button:
color_type: icon
color: var(--paper-card-background-color)
show_name: false
styles:
card: [border-radius: 50%, height: 60px, width: 60px]
Il template del pulsante per la selezione di una voce nei vari selettori, ne specifica il comportamento, ovvero l’azione eseguita ed il conseguente aspetto assunto:
radio_button_select:
template: ['radio_button']
variables:
value_select: "Spento"
color_selected: "lime"
color_unselected: ""
tap_action:
action: call-service
service: input_select.select_option
service_data:
entity_id: '[[[ return entity.entity_id ]]]'
option: '[[[ return variables.value_select ]]]'
styles:
icon:
- size: 100%
- color: >
[[[
if (entity.state==variables.value_select)
return variables.color_selected
else
return variables.color_unselected
]]]
Infine il template per i pulsanti di seek dei contenuti:
radio_seek_button:
template: ['radio_button']
variables:
var_dir: "next"
color: var(--paper-card-background-color)
icon: >
[[[ return 'mdi:skip-' + variables.var_dir ]]]
tap_action:
action: call-service
service: >
[[[ return 'input_select.select_' + variables.var_dir ]]]
service_data:
entity_id: input_select.radio_station
Siamo pronti quindi ad istanziare la nostra pulsantiera, usando i template sopra definiti. Ad esempio i pulsanti di selezione della sorgente e dello speaker, da inserire nella pila orizzontale di area header :
# Esempio selezione sorgente
- type: custom:button-card
template: radio_button_select
variables:
value_select: "Radio"
icon: mdi:radio
entity: input_select.radio_spotcast_source
# Esempio selezione player
- type: custom:button-card
template: radio_player_select
variables:
value_select: "Tutta_casa"
icon: mdi:home
entity: input_select.radio_player
Questi esempi vanno applicati a tutte le possibili opzioni dei nostri selettori, quindi per le tre sorgenti e per ciascuno speaker.
Interfaccia: la gestione della riproduzione
Nell’area panel1 posizioniamo i controlli dei singoli speaker per agire, durante la riproduzione, singolarmente su ognuno di essi. Allo scopo, utilizziamo una card entities
nella quale includiamo tante righe custom:slider-entity-raw
quanti sono i dispotivi/gruppi da gestire. Dividiamo i gruppi dai dispositivi con un elemento di tipo divider
e lo stesso facciamo tra gli speaker fisici ed il media_player.spotify
che posizioniamo in fondo alla lista. Con quest’ultimo potremo controllare in ogni momento la riproduzione (se attiva) sul nostro account Spotify.
# controllo volume singoli players
- type: custom:stack-in-card
mode: vertical
view_layout:
grid-area: panel1
cards:
- type: entities
entities:
- type: custom:slider-entity-row
entity: media_player.tutta_casa
icon: mdi:home
name: Tutta casa
hide_state: false
state_color: true
...
- type: divider
- type: 'custom:mini-media-player'
entity: media_player.spotify
name: Spotify
artwork: material
Nel pannello panel2 (a destra) andiamo a visualizzare le informazioni di riproduzione. Per far questo usiamo card di tipo conditional
con i vari contenuti, la cui visibilità è legata allo stato dei sensori che abbiamo definito in precedenza.
Ad esempio, il pannello di riproduzione della Radio mostra il nome ed il logo della radio on-air con i pulsanti per passare alle stazioni precedente/successiva nella lista:
# pannello RADIO playing
- type: conditional
conditions:
- entity: input_select.radio_spotcast_source
state: "Radio"
card:
type: custom:stack-in-card
cards:
- type: custom:button-card
show_name: false
show_label: false
show_icon: true
icon: mdi:music
show_entity_picture: >
[[[
if (states['binary_sensor.audio_riproduzione'].state=='off')
return "false"
else
return "true"
]]]
size: 90
entity_picture: >
[[[ return '/local/radio/' + states['sensor.radio_preset_logo'].state ]]]
styles:
card: [height: 339px, background: transparent]
entity_picture: [height: 98%, width: 98%]
tap_action:
action: fire-dom-event
browser_mod:
command: popup
title: Elenco stazioni radio
large: true
hide_header: false
deviceID:
- this
card:
!include popup_radio_spotcast.yaml
# Seek Radio
- type: custom:stack-in-card
mode: horizontal
cards:
# Previous station
- type: custom:button-card
template: radio_seek_button
variables:
var_dir: "previous"
# On-air station
- type: custom:button-card
color_type: card
color: var(--paper-card-background-color)
show_icon: false
name: "Riproduzione"
show_label: true
label: >
[[[ return states['input_select.radio_station'].state ]]]
tap_action:
action: fire-dom-event
browser_mod:
command: popup
title: Elenco stazioni radio
large: true
hide_header: false
deviceID:
- this
card:
!include popup_radio_spotcast.yaml
styles:
label:
- color: lime
card:
- height: 60px
# Next station
- type: custom:button-card
template: radio_seek_button
variables:
var_dir: "next"
Interfaccia: popup con le playlist
Ultimo tassello: il popup con le stazioni radio o le playlist Spotify. Utilizziamo il componente browser_mod
per attivare il popup, per il cui contenuto usiamo invece una card conditional
che mostra le stazioni radio o le playlist in base alla selezione dell’utente:
# POPUP Content
type: custom:state-switch
entity: input_select.radio_spotcast_source
states:
Spotify:
...
Radio:
...
Nel caso in cui si stia ascoltando la Radio, il popup contiene tanti pulsanti con template radio_preset_logo
definito in precedenza, quanti sono i canali radio disponibili, ad esempio:
type: custom:layout-card
layout_type: custom:horizontal-layout
layout:
width: 190
max_cols: 5
cards:
- type: custom:button-card
template: ['radio_preset_logo']
variables:
var_name: "Deejay Suona Italia"
var_option: "Radio Deejay Suona Italia"
...
Se invece è selezionato Spotify, il popup mostra l’elenco delle playlist disponibili:
type: custom:select-list-card
entity: input_select.radio_spotcast_playlist
title: Spotify playlist
icon: 'mdi:playlist-music'
max_options: 10
scroll_to_selected: true
truncate: true
Risultato finale
Il codice completo è disponibile qui.
Il risultato finale è mostrato in questo breve video:
Enjoy!