Ok, my holidays are over and I’m back home.
So it’s time for a new LLM script. ![]()
As music playback is the main driver for voice commands in our home, this was a non-brainer:
Group / ungroup music players:
Example usage:
- Expand the playback in the living room to the kitchen
- Add the bathroom to the playback in the living room
- Ungroup all players from the living room
alias: Media Player Group Manager
icon: mdi:speaker-multiple
description: >+
LLM Tool: Join/Unjoin media players, optionally clear existing members.
Hint:
Hint:
- If you are asked about joining / unjoing rooms, first look up the entity IDs
of the media players with the 'Entity Index' tool and the tag 'MediaPlayer' to
find the needed information. All of our media players are 'Inside' the house.
- When asked to e.g. add the kitchen to the playback in the living room, then
the living room has to be selected in the 'master' parameter in this case. The
players that should be added need to be selected in the parameter 'members'.
- The current grouping state of media_players can be checked using the
'Entity Index' tool ('Inside') with the tag 'MediaPlayer' and 'details' = true
mode: single
fields:
operation:
name: Operation
required: true
selector:
select:
options:
- join
- unjoin
- clear_members
master:
name: Master
description: Required for join and clear_members. The group leader / coordinator.
required: false
selector:
entity:
domain: media_player
members:
name: Members
description: >-
Players to join to the master (join) or to unjoin (unjoin). Multiple
allowed.
required: false
selector:
entity:
domain: media_player
multiple: true
ungroup_first:
name: Ungroup first
description: >-
If true (join only), unjoin the master and all provided members from any
groups before joining.
selector:
boolean: {}
default: false
replace_existing:
name: Replace existing members
description: >-
If true (join only), remove all current members from the master before
joining the provided members.
selector:
boolean: {}
default: false
sequence:
- action: logbook.log
data:
name: "LLM MEDIA GROUP:"
message: >-
{{ this.entity_id }} — op={{ operation }} master={{ master }} members={{
members|default([]) }} ungroup_first={{ ungroup_first|default(false) }}
replace_existing={{ replace_existing|default(false) }}
entity_id: "{{ this.entity_id }}"
- variables:
op: "{{ operation | lower }}"
allowed_ops:
- join
- unjoin
- clear_members
- variables:
normalized_members: |
{% set ns = namespace(lst=[]) %} {% if members is defined and members %}
{% if members is iterable and (members is not string) %}
{% for m in members %}
{% set ns.lst = ns.lst + [m] %}
{% endfor %}
{% else %}
{% set ns.lst = ns.lst + [members] %}
{% endif %}
{% endif %} {{ ns.lst }}
- variables:
members_final: |
{% set ns = namespace(lst=[]) %} {% for m in normalized_members %}
{% if (master is not defined or m != master) and (m not in ns.lst) %}
{% set ns.lst = ns.lst + [m] %}
{% endif %}
{% endfor %} {{ ns.lst }}
- variables:
errors: |
{% set ns = namespace(lst=[]) %} {% if op not in allowed_ops %}
{% set ns.lst = ns.lst + ['Invalid operation "' ~ op ~ '". Allowed: ' ~ (allowed_ops|join(', ')) ~ '.'] %}
{% endif %}
{% if op in ['join','clear_members'] and (not master) %}
{% set ns.lst = ns.lst + ['"master" is required for operation ' ~ op ~ '.'] %}
{% endif %}
{% if op == 'join' and (members_final|count) == 0 %}
{% set ns.lst = ns.lst + ['"members" must include at least one media_player for join.'] %}
{% endif %}
{% if op == 'clear_members' and (members_final|count) > 0 %}
{% set ns.lst = ns.lst + ['Do not provide "members" for clear_members.'] %}
{% endif %}
{% if op == 'unjoin' and (members_final|count) == 0 and (not master) %}
{% set ns.lst = ns.lst + ['Provide either "members" or a "master" to unjoin.'] %}
{% endif %}
{{ ns.lst }}
has_errors: "{{ (errors | count) > 0 }}"
- choose:
- conditions:
- condition: template
value_template: "{{ has_errors }}"
sequence:
- variables:
result: |
{{ {
'value': {
'status': 'error',
'messages': errors,
'received': {
'operation': op,
'master': master if master else None,
'members': members_final
}
}
} }}
- stop: ""
response_variable: result
- variables:
current_group: |
{% if master %}
{{ state_attr(master, 'group_members') or [] }}
{% else %}
{{ [] }}
{% endif %}
current_non_master: |
{% set ns = namespace(lst=[]) %} {% for m in current_group %}
{% if not master or m != master %}
{% set ns.lst = ns.lst + [m] %}
{% endif %}
{% endfor %} {{ ns.lst }}
pre_unjoin: |
{% if op == 'join' and (ungroup_first|default(false)) %}
{% set ns = namespace(lst=[]) %}
{% if master %}
{% set ns.lst = ns.lst + [master] %}
{% endif %}
{% for m in members_final %}
{% if m not in ns.lst %}
{% set ns.lst = ns.lst + [m] %}
{% endif %}
{% endfor %}
{{ ns.lst }}
{% else %}
{{ [] }}
{% endif %}
replace_unjoin: |
{% if op == 'join' and (replace_existing|default(false)) %}
{{ current_non_master }}
{% else %}
{{ [] }}
{% endif %}
ignored_flags: >
{% set ns = namespace(lst=[]) %} {% if op != 'join' and
(ungroup_first|default(false)) %}
{% set ns.lst = ns.lst + ['ungroup_first'] %}
{% endif %} {% if op != 'join' and (replace_existing|default(false)) %}
{% set ns.lst = ns.lst + ['replace_existing'] %}
{% endif %} {{ ns.lst }}
- choose:
- conditions:
- condition: template
value_template: "{{ op == 'join' }}"
sequence:
- repeat:
for_each: "{{ pre_unjoin }}"
sequence:
- action: media_player.unjoin
data:
entity_id: "{{ repeat.item }}"
- repeat:
for_each: "{{ replace_unjoin }}"
sequence:
- action: media_player.unjoin
data:
entity_id: "{{ repeat.item }}"
- choose:
- conditions:
- condition: template
value_template: "{{ (members_final | count) > 0 }}"
sequence:
- action: media_player.join
data:
entity_id: "{{ master }}"
group_members: "{{ members_final }}"
default: []
- variables:
result: |
{{ {
'value': {
'status': 'ok',
'operation': 'join',
'master': master,
'joined_members': members_final,
'ungroup_first': ungroup_first|default(false),
'replaced_existing': replace_existing|default(false),
'pre_unjoined': pre_unjoin,
'cleared_from_master': replace_unjoin,
'ignored_flags': ignored_flags
}
} }}
- stop: ""
response_variable: result
- conditions:
- condition: template
value_template: "{{ op == 'unjoin' }}"
sequence:
- choose:
- conditions:
- condition: template
value_template: "{{ (members_final | count) > 0 }}"
sequence:
- repeat:
for_each: "{{ members_final }}"
sequence:
- action: media_player.unjoin
data:
entity_id: "{{ repeat.item }}"
- variables:
result: >-
{{ {'value':
{'status':'ok','operation':'unjoin','unjoined':
members_final, 'ignored_flags': ignored_flags}} }}
- stop: ""
response_variable: result
- conditions:
- condition: template
value_template: "{{ master is not none }}"
sequence:
- action: media_player.unjoin
data:
entity_id: "{{ master }}"
- variables:
result: >-
{{ {'value':
{'status':'ok','operation':'unjoin','unjoined':[master],
'ignored_flags': ignored_flags}} }}
- stop: ""
response_variable: result
default:
- variables:
result: >-
{{ {'value': {'status':'error','messages':['Provide either
"members" or a "master" to unjoin.']}} }}
- stop: ""
response_variable: result
- conditions:
- condition: template
value_template: "{{ op == 'clear_members' }}"
sequence:
- repeat:
for_each: "{{ current_non_master }}"
sequence:
- action: media_player.unjoin
data:
entity_id: "{{ repeat.item }}"
- variables:
result: |
{{ {
'value': {
'status':'ok',
'operation':'clear_members',
'master': master,
'cleared_members': current_non_master
}
} }}
- stop: ""
response_variable: result
default:
- variables:
result: >-
{{ {'value': {'status':'error','messages':['Invalid operation. Use
join, unjoin or clear_members.']}} }}
- stop: ""
response_variable: result
I haven’t added anything to my prompt about this script so far as it seems to be detected and used fine.
But will most likely add a little sentence in the media section later and edit this script with it.
Maybe it won’t work well with smaller models otherwise.
edit:
Ok, I noticed a few times, that it didn’t react the right way to my requests.
I added a little bit more description to the script (updated the YAML code above) and added this to my prompt:
You can group media players with “Media Player Group Manager”. We use grouping a lot in the house. If we tell you e.g. that we want to listen to the same music in the kitchen as in the living room, this always means that you should group the kitchen media palyer (new member in this example) to the playback of the mediaplayer that is already playing in the living room (master in this example). Don’t simply start the same playback in the other room, always prefer grouping.