Skip to content

Commit

Permalink
added support for floors and music assistant
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnTheNerd committed Jun 25, 2024
1 parent e867bae commit 50ecf03
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 9 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ All fields in the sample configuration are required. This plugin has several fun

- Person: Defines every person and whether they are home. No examples as it is very self-explanatory.

- Music Assistant: Provides a few examples to use the mass.play_media service. Does not augment the LLM prompt itself.

- Laundry and Color Loop: Currently extremely custom and is mostly meant for me to use. Feel free to use them if they help you, but it's likely that you will need to change the templates.

`ignored_entities` ignores the entities given in the list. It is a substring search. If you want all entities to be part of the LLM prompt, simply make it an empty list.
Expand Down
3 changes: 2 additions & 1 deletion config.auth.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"laundry_enabled": false,
"media_player_enabled": true,
"person_enabled": true,
"color_loop_enabled": false
"color_loop_enabled": false,
"music_assistant_enabled": true
},
"weather": {
"station_id": "NL/s0000280"
Expand Down
3 changes: 2 additions & 1 deletion config.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"laundry_enabled": false,
"media_player_enabled": true,
"person_enabled": true,
"color_loop_enabled": false
"color_loop_enabled": false,
"music_assistant_enabled": false
},
"weather": {
"station_id": "NL/s0000280"
Expand Down
107 changes: 100 additions & 7 deletions plugins/homeassistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,39 @@ def __init__(self, config, utils):
self.media_player_enabled = config.get('media_player_enabled', False)
self.person_enabled = config.get('person_enabled', False)
self.color_loop_enabled = config.get('color_loop_enabled', False)
self.music_assistant_enabled = config.get('music_assistant_enabled', False)
self.shopping_list = ""
self.areas_template = """
{%- for area in areas() %}
{
"area_id": "{{area}}",
"area_name": "{{ area_name(area) }}",
"type": "area"
"type": "area",
{%- set ns = namespace() %}
{%- set ns.floor_id = "null" %}
{%- set ns.floor_name = "null" %}
{%- for floor in floors() %}
{%- if area in floor_areas(floor) %}
{%- set ns.floor_id = floor %}
{%- set ns.floor_name = floor_name(floor) %}
{%- endif %}
{%- endfor %}
"floor_id": "{{ns.floor_id}}",
"floor_name": "{{ns.floor_name}}"
},
{%- endfor %}
"""
self.title_template = """
Devices in area {{AREA_NAME}} (Area ID: {{AREA_ID}}):
{%- set ns = namespace() %}
{%- set ns.floor_id = "null" %}
{%- set ns.floor_name = "null" %}
{%- for floor in floors() %}
{%- if "{{AREA_ID}}" in floor_areas(floor) %}
{%- set ns.floor_id = floor %}
{%- set ns.floor_name = floor_name(floor) %}
{%- endif %}
{%- endfor %}
Devices in area {{AREA_NAME}} (Area ID: {{AREA_ID}} {%- if ns.floor_id != "null" -%}, Floor ID: {{ns.floor_id}} {%- endif -%}):
{%- set ignored_entities = {{IGNORED_ENTITIES}} %}
{%- for device in area_devices('{{AREA_ID}}') %}
{%- if not device_attr(device, "disabled_by") and not device_attr(device, "entry_type") and device_attr(device, "name") %}
Expand Down Expand Up @@ -94,14 +115,37 @@ def __init__(self, config, utils):
self.media_player_template = """
{%- for player in states.media_player %}
{%- if is_state(player.entity_id, 'playing') %}
{{ state_attr(player.entity_id, 'friendly_name') }} is playing {{ state_attr(player.entity_id, 'media_title') }} by {{ state_attr(player.entity_id, 'media_artist') }}.
{{ state_attr(player.entity_id, 'friendly_name') }} (Entity ID: {{player.entity_id}}) is playing {{ state_attr(player.entity_id, 'media_title') }} by {{ state_attr(player.entity_id, 'media_artist') }}.
{%- endif %}
{%- endfor %}"""
self.media_player_title_template = """
States of all media players (songs, movies, shows, videos) in the household
Detect, control and play media content, including songs and playlists, in specific rooms or zones within your smart home, using voice commands such as 'play hotel california in the living room' or 'resume playing music in the kitchen', and get instant access to your favorite media content with voice control.
{%- for player in states.media_player %}
- {{ state_attr(player.entity_id, 'friendly_name') }}
- {{ state_attr(player.entity_id, 'friendly_name') }} (Entity ID: {{player.entity_id}})
{%- endfor %}"""

self.mass_media_player_json_template = """
{%- for area in areas() %}
{%- for device in area_devices(area) %}
{%- if not device_attr(device, "disabled_by") and not device_attr(device, "entry_type") and device_attr(device, "name") %}
{%- for entity in device_entities(device) %}
{%- set entity_domain = entity.split('.')[0] %}
{%- if not is_state(entity,'unavailable') and not is_state(entity,'unknown') and not is_state(entity,"None") and not is_hidden_entity(entity) %}
{%- if entity_domain == "media_player" and state_attr(entity, 'app_id') %}
{
"entity_id": "{{entity}}",
"entity_name": "{{state_attr(entity, 'friendly_name')}}",
"area_name": "{{ area_name(area) }}",
"area_id": "{{ area }}"
},
{%- endif %}
{%- endif %}
{%- endfor %}
{%- endif %}
{%- endfor %}
{%- endfor %}
"""

self.laundry_template = """
{%- macro time_diff_in_words(timediff) %}
{%- if timediff.total_seconds() < 60 -%}
Expand Down Expand Up @@ -283,6 +327,22 @@ def get_areas(self):
area['area_name'] = area['area_name'].lower()
return areas

def get_music_assistant_entities(self):
music_assistant_entities_response = requests.post(f'{self.base_url}/api/template',
json={"template": self.mass_media_player_json_template},
headers={"Authorization": f"Bearer {self.access_token}"},
timeout=10)

# create a JSON from the result
# remove the trailing comma so the parsing wont fail
music_assistant_entities_json = f'[{music_assistant_entities_response.text[:-1]}]'
music_assistant_entities = json.loads(music_assistant_entities_json)
# make all music player names lowercase
# this will help the LLM understand as different capitalization can sometimes be tokenized differently
for entity in music_assistant_entities:
entity['entity_name'] = entity['entity_name'].lower()
return music_assistant_entities

def get_shopping_list(self):
shopping_list_response = requests.get(f'{self.base_url}/api/shopping_list',
headers={"Authorization": f"Bearer {self.access_token}"},
Expand Down Expand Up @@ -335,13 +395,26 @@ def get_llm_prompt_addition(self, document, user_prompt):
json={"template": summary_template_edited},
headers={"Authorization": f"Bearer {self.access_token}"},
timeout=10).text
if document['floor_id'] and document['floor_name']:
llm_prompt = llm_prompt + f"""
{document['area_name']} (Area ID: {document['area_id']}, located {document['floor_name']}, Floor ID: {document['floor_id']}):
llm_prompt = llm_prompt + f"""
{summary}
"""
examples.append(
(
f'Turn on all lights {document["floor_name"]}.',
'The lights ' + document["floor_name"] + ' are now on. $ActionRequired {"service": "light.turn_on", "floor_id": "' + document['floor_id'] + '"}'
)
)
else:
llm_prompt = llm_prompt + f"""
{document['area_name']} (Area ID: {document['area_id']}):
{summary}
"""
"""
examples.append(
(
f'Brighten the {document["area_name"]} lights.',
Expand Down Expand Up @@ -392,6 +465,26 @@ def get_llm_prompt_addition(self, document, user_prompt):
{summary}
"""
if self.music_assistant_enabled:
music_assistant_entities = self.get_music_assistant_entities()
if music_assistant_entities:
sample_entity = random.choice(music_assistant_entities)
sample_entity_name = sample_entity['entity_name']
sample_entity_id = sample_entity['entity_id']
sample_entity_area_id = sample_entity['area_id']
sample_entity_area_name = sample_entity['area_name']
examples.append(
(
f'Play Hotel California in the {sample_entity_area_name}',
f'Hotel California is now playing in the {sample_entity_area_name}. $ActionRequired ' + '{"service": "mass.play_media", "data": {"media_id": "Hotel California"}, "area_id": "' + sample_entity_area_id + '"}'
)
)
examples.append(
(
f'Play Hotel California in the {sample_entity_name}',
f'Hotel California is now playing in the {sample_entity_name}. $ActionRequired ' + '{"service": "mass.play_media", "data": {"media_id": "Hotel California"}, "entity_id": "' + sample_entity_id + '"}'
)
)
case "person":
summary = requests.post(f'{self.base_url}/api/template',
json={"template": self.person_template},
Expand Down

0 comments on commit 50ecf03

Please sign in to comment.