Hey everyone!
I recently transitioned from Amazon Echo devices to Home Assistant Voice, and one of the things I missed the most was playing Song Quiz. So, I decided to recreate it in Home Assistant using Music Assistant and conversation voice commands!
This blueprint allows you to play a music trivia game with up to four players. The game plays songs from a selected era, and players have to guess the song and artist to earn points. After five rounds, the player with the highest score wins!
For now you need to use your wake word, after every questions the voice satellite asks. I want to change that in the future when assist_satellite.start_conversation becomes available.
You can change the Spotify playlist that it will use for each era or just go with my defaults.
Update: Added an LLM Version, which uses a conversation agent to determine if the answers are correct and generates an end message. Everything else works the same.
Setup Instructions
Requirements:
- Music Assistant integration installed and configured
- A working Home Assistant Voice setup
- Four counters to track player scores:
counter.song_quiz_player_1
counter.song_quiz_player_2
counter.song_quiz_player_3
counter.song_quiz_player_4
How to Play
- Say βStart Song Quizβ to begin.
- Home Assistant will ask how many players are playing (1 player, 2 players,β¦)
- Choose a music era: 90s, 2000s, 2010s, or Now.
- Each player listens to a song snippet and tries to guess the song and artist.
- Players earn 25 points for guessing both correctly, 10 points for getting one right.
- After 5 rounds, the game announces the winner!
Voice Commands:
βStart Song Quizβ β Begins a new game
βStop Song Quizβ β Ends the game
Download the Blueprint
Paste the YAML into Automations > Blueprints, or grab it from my GitHub:
GitHub Link
Iβd love to hear feedback from the community! Let me know if you try it out or if you have any feature suggestions.
blueprint:
name: Song Quiz - Music trivia for Home Assistant - fully local
description: >-
A voice-controlled music trivia game using Home Assistant Voice and Music Assistant. <br>
Players guess songs based on a selected era, earning points for correct answers. <br>
The game lasts 5 rounds, and the player with the highest score wins.<br>
<br>
For now you need to use your wake word, after every questions the voice satellite asks. <br>
I want to change that in the future when assist_satellite.start_conversation becomes available.<br>
<br>
You can change the Spotify playlist that it will use for each era or just go with my defaults.<br>
<br>
π οΈ **Setup Instructions**<br>
<br>
**Requirements:**<br>
<br>
1. **Music Assistant** integration installed and configured <br>
2. A working **Home Assistant Voice** setup<br>
3. **Four counters** to track player scores:
* `counter.song_quiz_player_1`
* `counter.song_quiz_player_2`
* `counter.song_quiz_player_3`
* `counter.song_quiz_player_4`
<br>
**How to Play**<br>
<br>
1. Say **"Start Song Quiz"** to begin.<br>
2. Home Assistant will ask how many players are playing (1 player, 2 players,...)<br>
3. Choose a music era: **90s, 2000s, 2010s, or Now**.<br>
4. Each player listens to a song snippet and tries to guess the **song and artist**.<br>
5. Players earn **25 points** for guessing both correctly, **10 points** for getting one right.<br>
6. After 5 rounds, the game announces the winner!<br>
<br>
**Voice Commands:**<br>
<br>
π’ **"Start Song Quiz"** β Begins a new game<br>
π΄ **"Stop Song Quiz"** β Ends the game<br>
<br>
**Have fun playing!** <br>
domain: automation
input:
trigger_settings:
name: Trigger settings
icon: mdi:chat
description:
Settings for the trigger, you can adjust them, or translate them
to your own language
input:
start_trigger:
name: "Set Start Sentences"
description: "Customize the voice commands for starting song quiz."
selector:
text:
multiline: false
multiple: true
default:
- song quiz
- start song quiz
stop_trigger:
name: "Set Stop Sentences"
description: "Customize the voice commands for stopping song quiz."
selector:
text:
multiline: false
multiple: true
default:
- stop song quiz
game_rounds:
name: Game Rounds
description: How many rounds a game has.
default: 5
default_volume:
name: Default Volume
description: Default volume for playing the music.
default: 0.6
spotify_playlist:
name: Spotify Playlist Settings
icon: mdi:spotify
description:
Set the spotify playlist for the different eras.
input:
spotify_uris_90s:
name: Spotify URI for 90s
description: Playlist URI for the 90s era.
default: "spotify:playlist:37i9dQZF1DXbTxeAdrVG2l"
spotify_uris_2000s:
name: Spotify URI for 2000s
description: Playlist URI for the 2000s era.
default: "spotify:playlist:37i9dQZF1DX4o1oenSJRJd"
spotify_uris_2010s:
name: Spotify URI for 2010s
description: Playlist URI for the 2010s era.
default: "spotify:playlist:37i9dQZF1DX4UtSsGT1Sbe"
spotify_uris_now:
name: Spotify URI for Today
description: Playlist URI for current hits.
default: "spotify:playlist:37i9dQZEVXbMDoHDwVN2tF"
spotify_uris_default:
name: Spotify URI for fallback
description: Playlist URI as a fallback if no era is set.
default: "spotify:playlist:37i9dQZF1DX4UtSsGT1Sbe"
trigger:
- command: !input start_trigger
trigger: conversation
- trigger: conversation
command: !input stop_trigger
id: stop
actions:
- variables:
assist_area: "{{ trigger.device_id | device_attr('area_id') }}"
media_player_temp: >-
{% set players = area_entities(assist_area) | select('match', 'media_player\\..*') | list %}
{% set valid_players = players | select('in', integration_entities('music_assistant')) | list %}
{{ valid_players | first if valid_players else none }}
media_player: "{{ media_player_temp if media_player_temp else 'media_player.' ~ trigger.device_id.split('.')[1] }}"
- choose:
- conditions:
- condition: trigger
id:
- stop
sequence:
- action: media_player.media_pause
metadata: {}
data: {}
target:
entity_id: "{{ media_player }}"
- set_conversation_response: Song quiz stopped. Thanks for playing!
default:
- if:
- condition: template
value_template: "{{ is_state(media_player,'playing') }}"
then:
- action: media_player.media_pause
data: {}
target:
entity_id: "{{ media_player }}"
- action: counter.reset
target:
entity_id:
- counter.song_quiz_player_1
- counter.song_quiz_player_2
- counter.song_quiz_player_3
- counter.song_quiz_player_4
data: {}
- target:
device_id: "{{ trigger.device_id }}"
data:
message: >-
Welcome to Song Quiz! How many players are playing? Names are
optional.
action: assist_satellite.announce
- wait_for_trigger:
- trigger: conversation
command:
- "{number} players"
- "{number} player"
- "{number} players {names}"
continue_on_timeout: false
timeout:
hours: 0
minutes: 0
seconds: 30
milliseconds: 0
- variables:
num_players: >-
{% set mapping = {'One': 1, 'Two': 2, 'Three': 3, 'Four': 4} %} {{
mapping.get(wait.trigger.slots.number, 1) }}
names_specified: "{{ 'names' in wait.trigger.slots }}"
player_names: |-
{% if names_specified %}
{{ wait.trigger.slots.names.replace(' and ', ' ').split(' ')[:num_players] }}
{% else %}
{{ ['Player 1', 'Player 2', 'Player 3', 'Player 4'][:num_players] }}
{% endif %}
- target:
device_id: "{{ trigger.device_id }}"
data:
message: >-
{% if names_specified %}Welcome, {{ player_names | join(', ') }}!{%
else %}Great, {{ num_players }} Players! {% endif %}
action: assist_satellite.announce
- repeat:
sequence:
- target:
device_id: "{{ trigger.device_id }}"
data:
message: >-
Choose a music era 90s, 2000s, 2010s or today.
action: assist_satellite.announce
- wait_for_trigger:
- trigger: conversation
command:
- "{time}"
continue_on_timeout: false
timeout:
hours: 0
minutes: 0
seconds: 30
milliseconds: 0
- variables:
era: "{{ wait.trigger.slots.time | lower }}"
playlist_uri: >-
{% if era == '90s' %} !input spotify_uris_90s
{% elif era == '2000s' %} !input spotify_uris_2000s
{% elif era == '2010s' %} !input spotify_uris_2010s
{% elif era == 'today' %} !input spotify_uris_now
{% else %} !input spotify_uris_default {% endif %}
- if:
- condition: template
value_template: '{{ era in ["90s", "2000s", "2010s", "today"] }}'
then:
- action: music_assistant.play_media
data:
media_type: playlist
media_id: "{{ playlist_uri | string }}"
target:
entity_id: "{{ media_player }}"
- delay:
hours: 0
minutes: 0
seconds: 5
milliseconds: 0
- action: media_player.shuffle_set
data:
shuffle: true
target:
entity_id: "{{ media_player }}"
- delay:
hours: 0
minutes: 0
seconds: 2
milliseconds: 0
- data:
entity_id: "{{ media_player }}"
volume_level: 0.2
action: media_player.volume_set
until:
- condition: template
value_template: "{{ is_state(media_player,'playing') }}"
- variables:
round: 0
- repeat:
count: !input game_rounds
sequence:
- variables:
round: "{{ repeat.index }}"
- target:
device_id: "{{ trigger.device_id }}"
data:
message: Round {{ round }} begins!
action: assist_satellite.announce
- repeat:
count: "{{ num_players }}"
sequence:
- variables:
player: "{{ repeat.index }}"
default_volume: !input default_volume
current_volume: >-
{{ current_volume if current_volume else default_volume
}}
- action: media_player.media_next_track
data: {}
target:
entity_id: "{{ media_player }}"
- target:
device_id: "{{ trigger.device_id }}"
data:
message: "{{ player_names[player - 1] }}, listen to your song!"
action: assist_satellite.announce
- data:
entity_id: "{{ media_player }}"
volume_level: "{{ current_volume | float }}"
action: media_player.volume_set
- delay: "00:00:30"
- variables:
current_volume: "{{ state_attr(media_player, 'volume_level') }}"
- data:
entity_id: "{{ media_player }}"
volume_level: 0.2
action: media_player.volume_set
- target:
device_id: "{{ trigger.device_id }}"
data:
message: "{{ player_names[player - 1] }}, what is your guess?"
action: assist_satellite.announce
- wait_for_trigger:
- trigger: conversation
command:
- "{guess}"
continue_on_timeout: true
timeout:
hours: 0
minutes: 0
seconds: 30
milliseconds: 0
- variables:
answer: "{{ wait.trigger.slots.guess | default('User gave no answer') | lower }}"
correct_song: >-
{% set song = state_attr(media_player, 'media_title') |
lower %} {% if song is string %}
{% set song = song | regex_replace(find='\[.*?\]|\(.*?\)', replace='') %} {# Remove anything in brackets #}
{% set song = song | regex_replace(find='[0-9!?.,]', replace='') %} {# Remove numbers, exclamation marks, and punctuation #}
{{ song | trim }}
{% else %}
""
{% endif %}
correct_artist: >-
{% set artist = state_attr(media_player, 'media_artist')
| lower %} {% if artist is string %}
{% set artist = artist.split('/')[0] %} {# Take only the first part if there's a '/' #}
{% set artist = artist | regex_replace(find='[0-9&.-]', replace='', ignorecase=True) %} {# Remove numbers and '&' #}
{{ artist | trim }}
{% else %}
{% if artist is iterable %}
{{ artist | join(' / ') | regex_replace(find='[0-9&.-]', replace='', ignorecase=True) | trim }}
{% else %}
""
{% endif %}
{% endif %}
counter_entity: |-
{% if player == 1 %}
counter.song_quiz_player_1
{% elif player == 2 %}
counter.song_quiz_player_2
{% elif player == 3 %}
counter.song_quiz_player_3
{% else %}
counter.song_quiz_player_4
{% endif %}
- choose:
- conditions:
- condition: template
value_template: >-
{{ correct_song in answer | lower and
correct_artist in answer | lower }}
sequence:
- action: counter.set_value
data:
value: >-
{{ states(counter_entity) |
int + 25 }}
target:
entity_id: "{{ counter_entity }}"
- target:
device_id: "{{ trigger.device_id }}"
data:
message: >-
Bazinga! {{ player_names[player - 1] }} now has {{
states(counter_entity) }}
points.
action: assist_satellite.announce
- conditions:
- condition: template
value_template: >-
{{ correct_song in answer | lower or
correct_artist in answer | lower }}
sequence:
- action: counter.set_value
data:
value: >-
{{ states(counter_entity) |
int + 10 }}
target:
entity_id: "{{ counter_entity }}"
- target:
device_id: "{{ trigger.device_id }}"
data:
message: >-
Great Job! {{ player_names[player - 1] }} now has {{
states(counter_entity) }}
points.
action: assist_satellite.announce
default:
- target:
device_id: "{{ trigger.device_id }}"
data:
message: >-
Sorry thats incorrect! {{ player_names[player - 1] }} now
has {{ states(counter_entity) }}
points.
action: assist_satellite.announce
- if:
- condition: template
value_template: "{{ round != !input game_rounds }}"
then:
- target:
device_id: "{{ trigger.device_id }}"
data:
message: >-
End of round {{ round }}! Here are the standings: {{
player_names[0] }}: {{ states("counter.song_quiz_player_1") }}
points, {% if num_players > 1 %} {{ player_names[1] }}: {{
states("counter.song_quiz_player_2") }} points, {% endif %} {%
if num_players > 2 %} {{ player_names[2] }}: {{
states("counter.song_quiz_player_3") }} points, {% endif %} {%
if num_players > 3 %} {{ player_names[4] }}: {{
states("counter.song_quiz_player_4") }} points. {% endif %}
action: assist_satellite.announce
- target:
device_id: "{{ trigger.device_id }}"
data:
message: "Game over! Let's see the final scores:"
action: assist_satellite.announce
- repeat:
count: "{{ num_players }}"
sequence:
- variables:
player: "{{ repeat.index }}"
- target:
device_id: "{{ trigger.device_id }}"
data:
message: >-
{{ player_names[player - 1] }} scored {{
states("counter.song_quiz_player_1") if player == 1 else
states("counter.song_quiz_player_2") if player == 2 else
states("counter.song_quiz_player_3") if player == 3 else
states("counter.song_quiz_player_4") }} points.
action: assist_satellite.announce
- variables:
scores: |-
{% set score_list = [
states("counter.song_quiz_player_1") | int,
states("counter.song_quiz_player_2") | int,
states("counter.song_quiz_player_3") | int,
states("counter.song_quiz_player_4") | int
] %} {{ score_list[:num_players] }}
highest_score: "{{ scores | max }}"
winners: |-
{% if highest_score %}
{% set player_ids = [1, 2, 3, 4][:num_players] %}
{% set ns = namespace(winners=[]) %}
{% for i in range(num_players) %}
{% if scores[i] == highest_score %}
{% set ns.winners = ns.winners + [player_ids[i]] %}
{% endif %}
{% endfor %}
{{ ns.winners }}
{% else %}
[]
{% endif %}
- target:
device_id: "{{ trigger.device_id }}"
data:
message: |-
{% if not highest_score %}
No winner this time! Better luck next time!
{% elif winners | length == 1 %}
The winner is Player {{ winners[0] }}! Congratulations!
{% else %}
It's a tie between Players {{ winners | join(' and ') }}! Well played!
{% endif %}
action: assist_satellite.announce
- action: media_player.media_pause
metadata: {}
data: {}
target:
entity_id: "{{ media_player }}"
mode: restart