The Spotify integration only exposes the media_player.play_media service once it has an active playback context, which doesn't exist yet on a cold start; the fixed 2s delay after select_source wasn't long enough in that case even though it always worked once something had already played. Poll supported_features for the PLAY_MEDIA bit (up to 8s) before calling play_media instead of guessing a fixed delay. Also swap the Living Room/Bedroom source_list ID mapping in both the prompt (for display) and the functions (for select_source) -- it was backwards. Adds a test-harness regression case exercising the opaque Spotify Connect device IDs the source_list actually reports. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
241 lines
9.6 KiB
Plaintext
241 lines
9.6 KiB
Plaintext
- spec:
|
|
name: execute_services
|
|
description: Use this function to execute service of devices in Home Assistant.
|
|
parameters:
|
|
type: object
|
|
properties:
|
|
list:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
domain:
|
|
type: string
|
|
description: The domain of the service
|
|
service:
|
|
type: string
|
|
description: The service to be called
|
|
service_data:
|
|
type: object
|
|
description: The service data object to indicate what to control.
|
|
properties:
|
|
entity_id:
|
|
type: string
|
|
description: The entity_id retrieved from available devices. It
|
|
must start with domain, followed by dot character.
|
|
required:
|
|
- entity_id
|
|
required:
|
|
- domain
|
|
- service
|
|
- service_data
|
|
function:
|
|
type: native
|
|
name: execute_service
|
|
|
|
|
|
# Spotify Music Control Functions
|
|
|
|
- spec:
|
|
name: search_spotify
|
|
description: >-
|
|
Search Spotify for an artist, album, track, or playlist and return the Spotify URI.
|
|
Use this before playing music to get the URI. Use this whenever the user asks to play
|
|
or queue a specific song, artist, album, or playlist — even if the name contains a
|
|
number (e.g. "Back on 74", "1979", "Blink 182"). A number that is part of a
|
|
song/artist/album name is NOT a volume level; only use control_playback's volume_set
|
|
if the user explicitly says "volume" or "percent".
|
|
parameters:
|
|
type: object
|
|
properties:
|
|
query:
|
|
type: string
|
|
description: Artist, album, track, or playlist name to search for (e.g., "Coldplay", "Parachutes", "Yellow", "Today's Top Hits")
|
|
type:
|
|
type: string
|
|
enum: [artist, album, track, playlist]
|
|
description: Type of content to search for. Playlist searches check your personal playlists first, then fall back to public Spotify playlists.
|
|
required:
|
|
- query
|
|
- type
|
|
function:
|
|
type: script
|
|
sequence:
|
|
- service: spotify_voice_assistant.search
|
|
response_variable: _function_result
|
|
data:
|
|
query: "{{ query }}"
|
|
type: "{{ type }}"
|
|
|
|
- spec:
|
|
name: play_music
|
|
description: Play music immediately on a Spotify Connect device using a Spotify URI, replacing current playback. You must first call search_spotify to get the URI. Use this when user says "play".
|
|
parameters:
|
|
type: object
|
|
properties:
|
|
spotify_uri:
|
|
type: string
|
|
description: Spotify URI from search_spotify (e.g., "spotify:artist:4gzpq5DPGxSnKTe4SA8HAU")
|
|
type:
|
|
type: string
|
|
enum: [artist, album, track, playlist]
|
|
description: The exact "type" value returned by search_spotify for this URI. Required — passing the wrong value causes playback to silently fail.
|
|
room:
|
|
type: string
|
|
description: Spotify Connect source/device name to play on, matching an entry in media_player.spotify_bhetherman's source_list exactly (e.g., "Living Room")
|
|
required:
|
|
- spotify_uri
|
|
- type
|
|
- room
|
|
function:
|
|
type: script
|
|
sequence:
|
|
- service: media_player.select_source
|
|
target:
|
|
entity_id: media_player.spotify_bhetherman
|
|
data:
|
|
source: "{{ room | replace('Living Room', 'd0166d6f83796b0f9059b0adf0f6cb0d81fa6376') | replace('Bedroom', 'a334c8ba989367b52303eaa9dbc424cdd83012b5') }}"
|
|
- repeat:
|
|
while:
|
|
- condition: template
|
|
value_template: >-
|
|
{{ repeat.index <= 8 and
|
|
(state_attr('media_player.spotify_bhetherman', 'supported_features') | int(0)
|
|
| bitwise_and(512)) != 512 }}
|
|
sequence:
|
|
- delay: "00:00:01"
|
|
- service: media_player.play_media
|
|
target:
|
|
entity_id: media_player.spotify_bhetherman
|
|
data:
|
|
media_content_id: "{{ spotify_uri }}"
|
|
media_content_type: "{{ type }}"
|
|
enqueue: replace
|
|
|
|
- spec:
|
|
name: queue_music
|
|
description: >-
|
|
Queue or play music using a Spotify URI from search_spotify. Use this whenever the
|
|
user says "queue" or "add to queue" — always use this function for queue requests,
|
|
regardless of whether the content is a track, album, artist, or playlist; the
|
|
correct behavior for each type is handled automatically. (Individual tracks are
|
|
added to the queue without interrupting playback; since Spotify cannot queue a whole
|
|
album/artist/playlist, those start playing immediately instead.)
|
|
parameters:
|
|
type: object
|
|
properties:
|
|
spotify_uri:
|
|
type: string
|
|
description: Spotify URI from search_spotify (e.g., "spotify:track:4gzpq5DPGxSnKTe4SA8HAU")
|
|
type:
|
|
type: string
|
|
enum: [artist, album, track, playlist]
|
|
description: The exact "type" value returned by search_spotify for this URI.
|
|
room:
|
|
type: string
|
|
description: Spotify Connect source/device name to queue on, matching an entry in media_player.spotify_bhetherman's source_list exactly (e.g., "Living Room")
|
|
required:
|
|
- spotify_uri
|
|
- type
|
|
- room
|
|
function:
|
|
type: script
|
|
sequence:
|
|
- service: media_player.select_source
|
|
target:
|
|
entity_id: media_player.spotify_bhetherman
|
|
data:
|
|
source: "{{ room | replace('Living Room', 'd0166d6f83796b0f9059b0adf0f6cb0d81fa6376') | replace('Bedroom', 'a334c8ba989367b52303eaa9dbc424cdd83012b5') }}"
|
|
- repeat:
|
|
while:
|
|
- condition: template
|
|
value_template: >-
|
|
{{ repeat.index <= 8 and
|
|
(state_attr('media_player.spotify_bhetherman', 'supported_features') | int(0)
|
|
| bitwise_and(512)) != 512 }}
|
|
sequence:
|
|
- delay: "00:00:01"
|
|
- service: media_player.play_media
|
|
target:
|
|
entity_id: media_player.spotify_bhetherman
|
|
data:
|
|
media_content_id: "{{ spotify_uri }}"
|
|
media_content_type: "{{ type }}"
|
|
enqueue: "{{ 'add' if type == 'track' else 'replace' }}"
|
|
|
|
- spec:
|
|
name: control_playback
|
|
description: >-
|
|
Control playback of music that is ALREADY PLAYING (pause, resume, stop, next track,
|
|
previous track, set volume, shuffle) on the currently active Spotify Connect session.
|
|
Only use volume_set when the user explicitly says the word "volume" or "percent"
|
|
(e.g. "turn the volume up", "set volume to 50", "50 percent volume").
|
|
Do NOT use this function to play a requested song, artist, or album — a number in a
|
|
song/artist/album name (e.g. "Back on 74", "Blink 182", "Sunset 2005") is part of the
|
|
title, never a volume level. Use search_spotify + play_music for any request to play
|
|
or start specific music, even if it contains a number.
|
|
parameters:
|
|
type: object
|
|
properties:
|
|
action:
|
|
type: string
|
|
enum: [pause, play, stop, next_track, previous_track, volume_set, shuffle_on, shuffle_off]
|
|
description: Playback control action
|
|
volume_level:
|
|
type: number
|
|
description: Volume level (0-100) for volume_set action only. Only set this when the user said the word "volume" or "percent".
|
|
required:
|
|
- action
|
|
function:
|
|
type: script
|
|
sequence:
|
|
- choose:
|
|
- conditions: "{{ action == 'pause' }}"
|
|
sequence:
|
|
- service: media_player.media_pause
|
|
target:
|
|
entity_id: media_player.spotify_bhetherman
|
|
- conditions: "{{ action == 'play' }}"
|
|
sequence:
|
|
- service: media_player.media_play
|
|
target:
|
|
entity_id: media_player.spotify_bhetherman
|
|
- conditions: "{{ action == 'stop' }}"
|
|
sequence:
|
|
# media_player.spotify_bhetherman does not support the media_stop
|
|
# service (STOP is not in its supported_features); Spotify Connect has
|
|
# no separate stop concept, so route "stop" through pause instead.
|
|
- service: media_player.media_pause
|
|
target:
|
|
entity_id: media_player.spotify_bhetherman
|
|
- conditions: "{{ action == 'next_track' }}"
|
|
sequence:
|
|
- service: media_player.media_next_track
|
|
target:
|
|
entity_id: media_player.spotify_bhetherman
|
|
- conditions: "{{ action == 'previous_track' }}"
|
|
sequence:
|
|
- service: media_player.media_previous_track
|
|
target:
|
|
entity_id: media_player.spotify_bhetherman
|
|
- conditions: "{{ action == 'volume_set' }}"
|
|
sequence:
|
|
- service: media_player.volume_set
|
|
target:
|
|
entity_id: media_player.spotify_bhetherman
|
|
data:
|
|
volume_level: "{{ volume_level / 100 }}"
|
|
- conditions: "{{ action == 'shuffle_on' }}"
|
|
sequence:
|
|
- service: media_player.shuffle_set
|
|
target:
|
|
entity_id: media_player.spotify_bhetherman
|
|
data:
|
|
shuffle: true
|
|
- conditions: "{{ action == 'shuffle_off' }}"
|
|
sequence:
|
|
- service: media_player.shuffle_set
|
|
target:
|
|
entity_id: media_player.spotify_bhetherman
|
|
data:
|
|
shuffle: false |