From 5dec83354454900fd6ab5b25f0db385acdb10fd2 Mon Sep 17 00:00:00 2001 From: Pierre Wessman <4029607+pierrewessman@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:18:10 +0100 Subject: [PATCH] configurable speaker based on device_id --- agent/src/agent.py | 3 ++- agent/src/backend.py | 12 +++++++++--- agent/src/plugins/base_plugin.py | 1 + agent/src/plugins/music/plugin.py | 27 ++++++++++++++++++++++----- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/agent/src/agent.py b/agent/src/agent.py index 5dab9d0..f8fad21 100644 --- a/agent/src/agent.py +++ b/agent/src/agent.py @@ -20,7 +20,8 @@ class Agent: history: list = None, tools: Union[None, list] = None, prompts: Union[None, list] = None, - language: str = "en" + language: str = "en", + device_id: str = "" ): """ Ask the agent a question. diff --git a/agent/src/backend.py b/agent/src/backend.py index d6ad555..7f64889 100644 --- a/agent/src/backend.py +++ b/agent/src/backend.py @@ -23,6 +23,7 @@ with open("config.yml", "r", encoding="utf8") as file: def load_plugins(config): tools = [] prompts = [] + plugin_instances = [] for plugin_folder in os.listdir("./" + PLUGINS_FOLDER): if ( @@ -33,15 +34,16 @@ def load_plugins(config): module_path = f"{PLUGINS_FOLDER}.{plugin_folder}.plugin" module = importlib.import_module(module_path) plugin = module.Plugin(config) + plugin_instances.append(plugin) tools += plugin.tools() prompt = plugin.prompt() if prompt is not None: prompts.append(prompt) - return tools, prompts + return tools, prompts, plugin_instances -tools, prompts = load_plugins(config) +tools, prompts, plugins = load_plugins(config) app = FastAPI() app.mount("/static", StaticFiles(directory="static"), name="static") @@ -89,5 +91,9 @@ def ask(q: Question): if q.history: history = [q.model_dump() for q in q.history] - answer = agent.ask(question=q.question, history=history, tools=tools, prompts=prompts, language=q.language) + # Set device_id on all plugin instances + for plugin in plugins: + plugin.device_id = q.device_id + + answer = agent.ask(question=q.question, history=history, tools=tools, prompts=prompts, language=q.language, device_id=q.device_id) return {"question": q.question, "answer": answer, "history": q.history} diff --git a/agent/src/plugins/base_plugin.py b/agent/src/plugins/base_plugin.py index 8cbe8b9..6c80dde 100644 --- a/agent/src/plugins/base_plugin.py +++ b/agent/src/plugins/base_plugin.py @@ -7,6 +7,7 @@ class BasePlugin: def __init__(self, config: dict) -> None: self.config = config self.homeassistant = HomeAssistant(config) + self.device_id = "" def prompt(self) -> str | None: return diff --git a/agent/src/plugins/music/plugin.py b/agent/src/plugins/music/plugin.py index 4b8d5b6..b245cea 100644 --- a/agent/src/plugins/music/plugin.py +++ b/agent/src/plugins/music/plugin.py @@ -15,17 +15,32 @@ class PlayMusicInput(BaseModel): class Plugin(BasePlugin): def __init__(self, config: dict) -> None: super().__init__(config=config) - self.entity_id = self.config["plugins"]["music"]["default_speaker"] self.spotify = spotipy.Spotify( auth_manager=SpotifyOAuth( - scope="user-library-read", + scope="user-library-read", redirect_uri="http://localhost:8080", client_id=self.config["plugins"]["music"]["spotify_client_id"], client_secret=self.config["plugins"]["music"]["spotify_client_secret"] ) ) + def _get_speaker_for_device(self, device_id: str) -> str: + """ + Get the appropriate speaker entity_id based on device_id. + Falls back to default_speaker if device_id is not found or empty. + """ + device_speakers = self.config["plugins"]["music"].get("device_speakers", {}) + + if device_id and device_id in device_speakers: + speaker = device_speakers[device_id] + logging.info(f"Using device-specific speaker for {device_id}: {speaker}") + return speaker + + default_speaker = self.config["plugins"]["music"]["default_speaker"] + logging.info(f"Using default speaker: {default_speaker}") + return default_speaker + def _search(self, query: str, limit: int = 10): _result = self.spotify.search(query, limit=limit) result = [] @@ -45,9 +60,10 @@ class Plugin(BasePlugin): Play music using a search query. """ track = self._search(input.query, limit=1)[0] - logging.info(f"Playing {track['name']} by {', '.join(track['artists'])}") + speaker = self._get_speaker_for_device(self.device_id) + logging.info(f"Playing {track['name']} by {', '.join(track['artists'])} on {speaker}") payload = { - "entity_id": self.entity_id, + "entity_id": speaker, "media_content_id": track["uri"], "media_content_type": "music", "enqueue": "play", @@ -59,7 +75,8 @@ class Plugin(BasePlugin): """ Stop playback of music. """ + speaker = self._get_speaker_for_device(self.device_id) self.homeassistant.call_api( - f"services/media_player/media_pause", payload={"entity_id": self.entity_id} + f"services/media_player/media_pause", payload={"entity_id": speaker} ) return json.dumps({"status": "success", "message": f"Music paused."})