Skip to content

Commit 0d93486

Browse files
committed
hass integration now moved to hass component
1 parent b3a3207 commit 0d93486

File tree

6 files changed

+88
-317
lines changed

6 files changed

+88
-317
lines changed

music_assistant/constants.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""All constants for Music Assistant."""
22

3-
__version__ = "0.0.31"
3+
__version__ = "0.0.32"
44
REQUIRED_PYTHON_VER = "3.7"
55

66
CONF_USERNAME = "username"
@@ -32,4 +32,11 @@
3232
EVENT_SHUTDOWN = "application shutdown"
3333
EVENT_PROVIDER_REGISTERED = "provider registered"
3434
EVENT_PLAYER_CONTROL_REGISTERED = "player control registered"
35+
EVENT_PLAYER_CONTROL_UNREGISTERED = "player control unregistered"
3536
EVENT_PLAYER_CONTROL_UPDATED = "player control updated"
37+
EVENT_SET_PLAYER_CONTROL_STATE = "set player control state"
38+
39+
# websocket commands
40+
EVENT_REGISTER_PLAYER_CONTROL = "register player control"
41+
EVENT_UNREGISTER_PLAYER_CONTROL = "unregister player control"
42+
EVENT_UPDATE_PLAYER_CONTROL = "update player control"

music_assistant/models/player.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Any, List
77

88
from mashumaro import DataClassDictMixin
9+
from music_assistant.constants import EVENT_SET_PLAYER_CONTROL_STATE
910
from music_assistant.models.config_entry import ConfigEntry
1011
from music_assistant.utils import CustomIntEnum
1112

@@ -84,7 +85,7 @@ class PlayerControlType(CustomIntEnum):
8485

8586

8687
@dataclass
87-
class PlayerControl(DataClassDictMixin):
88+
class PlayerControl:
8889
"""
8990
Model for a player control.
9091
@@ -93,7 +94,29 @@ class PlayerControl(DataClassDictMixin):
9394
"""
9495

9596
type: PlayerControlType = PlayerControlType.UNKNOWN
96-
id: str = ""
97+
control_id: str = ""
98+
provider: str = ""
9799
name: str = ""
98100
state: Any = None
99-
set_state: Any = None
101+
102+
async def async_set_state(self, new_state: Any):
103+
"""Handle command to set the state for a player control."""
104+
# by default we just signal an event on the eventbus
105+
# pickup this event (e.g. from the websocket api)
106+
# or override this method with your own implementation.
107+
108+
# pylint: disable=no-member
109+
self.mass.signal_event(
110+
EVENT_SET_PLAYER_CONTROL_STATE,
111+
{"control_id": self.control_id, "state": new_state},
112+
)
113+
114+
def to_dict(self):
115+
"""Return dict representation of this playercontrol."""
116+
return {
117+
"type": int(self.type),
118+
"control_id": self.control_id,
119+
"provider": self.provider,
120+
"name": self.name,
121+
"state": self.state,
122+
}

music_assistant/player_manager.py

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import logging
44
from datetime import datetime
5-
from typing import Any, List, Optional
5+
from typing import List, Optional
66

77
from music_assistant.constants import (
88
CONF_ENABLED,
@@ -12,6 +12,8 @@
1212
EVENT_PLAYER_CONTROL_REGISTERED,
1313
EVENT_PLAYER_CONTROL_UPDATED,
1414
EVENT_PLAYER_REMOVED,
15+
EVENT_REGISTER_PLAYER_CONTROL,
16+
EVENT_UNREGISTER_PLAYER_CONTROL,
1517
)
1618
from music_assistant.models.config_entry import ConfigEntry, ConfigEntryType
1719
from music_assistant.models.media_types import MediaItem, MediaType, Track
@@ -51,6 +53,14 @@ def __init__(self, mass):
5153
self._poll_ticks = 0
5254
self._controls = {}
5355
self._player_controls_config_entries = []
56+
self.mass.add_event_listener(
57+
self.__handle_websocket_player_control_event,
58+
[
59+
EVENT_REGISTER_PLAYER_CONTROL,
60+
EVENT_UNREGISTER_PLAYER_CONTROL,
61+
EVENT_PLAYER_CONTROL_UPDATED,
62+
],
63+
)
5464

5565
async def async_setup(self):
5666
"""Async initialize of module."""
@@ -162,26 +172,39 @@ async def async_update_player(self, player: Player):
162172

163173
async def async_register_player_control(self, control: PlayerControl):
164174
"""Register a playercontrol with the player manager."""
165-
self._controls[control.id] = control
166-
LOGGER.info("New %s PlayerControl registered: %s", control.type, control.name)
167-
self.mass.signal_event(EVENT_PLAYER_CONTROL_REGISTERED, control.id)
175+
# control.mass = self.mass
176+
control.mass = self.mass
177+
control.type = PlayerControlType(control.type)
178+
self._controls[control.control_id] = control
179+
LOGGER.info(
180+
"New PlayerControl (%s) registered: %s\\%s",
181+
control.type,
182+
control.provider,
183+
control.name,
184+
)
168185
await self.__async_create_playercontrol_config_entries()
169186
# update all players as they may want to use this control
170187
for player in self._players.values():
171188
self.mass.add_job(self.async_update_player(player))
172189

173-
async def async_update_player_control(self, control_id: str, new_state: Any):
190+
async def async_update_player_control(self, control: PlayerControl):
174191
"""Update a playercontrol's state on the player manager."""
175-
control = self._controls.get(control_id)
176-
if not control or control.state == new_state:
192+
if control.control_id not in self._controls:
193+
return await self.async_register_player_control(control)
194+
new_state = control.state
195+
if self._controls[control.control_id].state == new_state:
177196
return
178-
LOGGER.info("PlayerControl %s updated - new state: %s", control.name, new_state)
179-
control.state = new_state
180-
self.mass.signal_event(EVENT_PLAYER_CONTROL_UPDATED, control.id)
197+
self._controls[control.control_id].state = new_state
198+
LOGGER.debug(
199+
"PlayerControl %s\\%s updated - new state: %s",
200+
control.provider,
201+
control.name,
202+
new_state,
203+
)
181204
# update all players using this playercontrol
182205
for player_id, player in self._players.items():
183206
conf = self.mass.config.player_settings[player_id]
184-
if control.id in [
207+
if control.control_id in [
185208
conf.get(CONF_POWER_CONTROL),
186209
conf.get(CONF_VOLUME_CONTROL),
187210
]:
@@ -361,7 +384,7 @@ async def async_cmd_power_on(self, player_id: str) -> None:
361384
if player_config.get(CONF_POWER_CONTROL):
362385
control = self.get_player_control(player_config[CONF_POWER_CONTROL])
363386
if control:
364-
self.mass.add_job(control.set_state, control.id, True)
387+
await control.async_set_state(True)
365388

366389
async def async_cmd_power_off(self, player_id: str) -> None:
367390
"""
@@ -377,7 +400,7 @@ async def async_cmd_power_off(self, player_id: str) -> None:
377400
if player_config.get(CONF_POWER_CONTROL):
378401
control = self.get_player_control(player_config[CONF_POWER_CONTROL])
379402
if control:
380-
self.mass.add_job(control.set_state, control.id, False)
403+
await control.async_set_state(False)
381404
# handle group power
382405
if player.is_group_player:
383406
# player is group, turn off all childs
@@ -433,7 +456,7 @@ async def async_cmd_volume_set(self, player_id: str, volume_level: int) -> None:
433456
if player_config.get(CONF_VOLUME_CONTROL):
434457
control = self.get_player_control(player_config[CONF_VOLUME_CONTROL])
435458
if control:
436-
self.mass.add_job(control.set_state, control.id, volume_level)
459+
await control.async_set_state(volume_level)
437460
# just force full volume on actual player if volume is outsourced to volumecontrol
438461
await player_prov.async_cmd_volume_set(player_id, 100)
439462
# handle group volume
@@ -645,7 +668,8 @@ async def __async_create_playercontrol_config_entries(self):
645668
power_controls = self.get_player_controls(PlayerControlType.POWER)
646669
if power_controls:
647670
controls = [
648-
{"text": item.name, "value": item.id} for item in power_controls
671+
{"text": f"{item.provider}: {item.name}", "value": item.control_id}
672+
for item in power_controls
649673
]
650674
entries.append(
651675
ConfigEntry(
@@ -659,7 +683,8 @@ async def __async_create_playercontrol_config_entries(self):
659683
volume_controls = self.get_player_controls(PlayerControlType.VOLUME)
660684
if volume_controls:
661685
controls = [
662-
{"text": item.name, "value": item.id} for item in volume_controls
686+
{"text": f"{item.provider}: {item.name}", "value": item.control_id}
687+
for item in volume_controls
663688
]
664689
entries.append(
665690
ConfigEntry(
@@ -695,3 +720,13 @@ def __player_updated(self, player_id: str, changed_value: str):
695720
self.mass.add_job(self.async_update_player(child_player))
696721
if player_id in self._player_queues and player.active_queue == player_id:
697722
self.mass.add_job(self._player_queues[player_id].async_update_state())
723+
724+
async def __handle_websocket_player_control_event(self, msg, msg_details):
725+
"""Handle player controls over the websockets api."""
726+
if msg in [EVENT_REGISTER_PLAYER_CONTROL, EVENT_PLAYER_CONTROL_UPDATED]:
727+
# create or update a playercontrol registered through the websockets api
728+
control = PlayerControl(**msg_details)
729+
await self.async_update_player_control(control)
730+
# send confirmation to the client that the register was successful
731+
if msg == EVENT_PLAYER_CONTROL_REGISTERED:
732+
self.mass.signal_event(EVENT_PLAYER_CONTROL_REGISTERED, control)

0 commit comments

Comments
 (0)