Use entity services in bluesound integration (#129266)

This commit is contained in:
Louis Christ 2024-12-17 20:44:38 +01:00 committed by GitHub
parent c9ca1f63ea
commit 9c26654db7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 142 additions and 164 deletions

View File

@ -14,7 +14,6 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN from .const import DOMAIN
from .services import setup_services
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
@ -36,7 +35,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Bluesound.""" """Set up the Bluesound."""
if DOMAIN not in hass.data: if DOMAIN not in hass.data:
hass.data[DOMAIN] = [] hass.data[DOMAIN] = []
setup_services(hass)
return True return True

View File

@ -6,7 +6,7 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/bluesound", "documentation": "https://www.home-assistant.io/integrations/bluesound",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["pyblu==1.0.4"], "requirements": ["pyblu==2.0.0"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_musc._tcp.local." "type": "_musc._tcp.local."

View File

@ -28,18 +28,26 @@ from homeassistant.const import CONF_HOST, CONF_HOSTS, CONF_NAME, CONF_PORT
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from homeassistant.exceptions import ServiceValidationError from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv, issue_registry as ir from homeassistant.helpers import (
config_validation as cv,
entity_platform,
issue_registry as ir,
)
from homeassistant.helpers.device_registry import ( from homeassistant.helpers.device_registry import (
CONNECTION_NETWORK_MAC, CONNECTION_NETWORK_MAC,
DeviceInfo, DeviceInfo,
format_mac, format_mac,
) )
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from .const import ATTR_BLUESOUND_GROUP, ATTR_MASTER, DOMAIN, INTEGRATION_TITLE from .const import ATTR_BLUESOUND_GROUP, ATTR_MASTER, DOMAIN, INTEGRATION_TITLE
from .utils import format_unique_id from .utils import dispatcher_join_signal, dispatcher_unjoin_signal, format_unique_id
if TYPE_CHECKING: if TYPE_CHECKING:
from . import BluesoundConfigEntry from . import BluesoundConfigEntry
@ -51,6 +59,11 @@ SCAN_INTERVAL = timedelta(minutes=15)
DATA_BLUESOUND = DOMAIN DATA_BLUESOUND = DOMAIN
DEFAULT_PORT = 11000 DEFAULT_PORT = 11000
SERVICE_CLEAR_TIMER = "clear_sleep_timer"
SERVICE_JOIN = "join"
SERVICE_SET_TIMER = "set_sleep_timer"
SERVICE_UNJOIN = "unjoin"
NODE_OFFLINE_CHECK_TIMEOUT = 180 NODE_OFFLINE_CHECK_TIMEOUT = 180
NODE_RETRY_INITIATION = timedelta(minutes=3) NODE_RETRY_INITIATION = timedelta(minutes=3)
@ -130,6 +143,18 @@ async def async_setup_entry(
config_entry.runtime_data.sync_status, config_entry.runtime_data.sync_status,
) )
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_SET_TIMER, None, "async_increase_timer"
)
platform.async_register_entity_service(
SERVICE_CLEAR_TIMER, None, "async_clear_timer"
)
platform.async_register_entity_service(
SERVICE_JOIN, {vol.Required(ATTR_MASTER): cv.entity_id}, "async_join"
)
platform.async_register_entity_service(SERVICE_UNJOIN, None, "async_unjoin")
hass.data[DATA_BLUESOUND].append(bluesound_player) hass.data[DATA_BLUESOUND].append(bluesound_player)
async_add_entities([bluesound_player], update_before_add=True) async_add_entities([bluesound_player], update_before_add=True)
@ -175,13 +200,12 @@ class BluesoundPlayer(MediaPlayerEntity):
self._status: Status | None = None self._status: Status | None = None
self._inputs: list[Input] = [] self._inputs: list[Input] = []
self._presets: list[Preset] = [] self._presets: list[Preset] = []
self._muted = False
self._master: BluesoundPlayer | None = None
self._is_master = False
self._group_name: str | None = None self._group_name: str | None = None
self._group_list: list[str] = [] self._group_list: list[str] = []
self._bluesound_device_name = sync_status.name self._bluesound_device_name = sync_status.name
self._player = player self._player = player
self._is_leader = False
self._leader: BluesoundPlayer | None = None
self._attr_unique_id = format_unique_id(sync_status.mac, port) self._attr_unique_id = format_unique_id(sync_status.mac, port)
# there should always be one player with the default port per mac # there should always be one player with the default port per mac
@ -250,6 +274,22 @@ class BluesoundPlayer(MediaPlayerEntity):
name=f"bluesound.poll_sync_status_loop_{self.host}:{self.port}", name=f"bluesound.poll_sync_status_loop_{self.host}:{self.port}",
) )
assert self._sync_status.id is not None
self.async_on_remove(
async_dispatcher_connect(
self.hass,
dispatcher_join_signal(self.entity_id),
self.async_add_follower,
)
)
self.async_on_remove(
async_dispatcher_connect(
self.hass,
dispatcher_unjoin_signal(self._sync_status.id),
self.async_remove_follower,
)
)
async def async_will_remove_from_hass(self) -> None: async def async_will_remove_from_hass(self) -> None:
"""Stop the polling task.""" """Stop the polling task."""
await super().async_will_remove_from_hass() await super().async_will_remove_from_hass()
@ -317,25 +357,25 @@ class BluesoundPlayer(MediaPlayerEntity):
self._group_list = self.rebuild_bluesound_group() self._group_list = self.rebuild_bluesound_group()
if sync_status.master is not None: if sync_status.leader is not None:
self._is_master = False self._is_leader = False
master_id = f"{sync_status.master.ip}:{sync_status.master.port}" leader_id = f"{sync_status.leader.ip}:{sync_status.leader.port}"
master_device = [ leader_device = [
device device
for device in self.hass.data[DATA_BLUESOUND] for device in self.hass.data[DATA_BLUESOUND]
if device.id == master_id if device.id == leader_id
] ]
if master_device and master_id != self.id: if leader_device and leader_id != self.id:
self._master = master_device[0] self._leader = leader_device[0]
else: else:
self._master = None self._leader = None
_LOGGER.error("Master not found %s", master_id) _LOGGER.error("Leader not found %s", leader_id)
else: else:
if self._master is not None: if self._leader is not None:
self._master = None self._leader = None
slaves = self._sync_status.slaves followers = self._sync_status.followers
self._is_master = slaves is not None self._is_leader = followers is not None
self.async_write_ha_state() self.async_write_ha_state()
@ -355,7 +395,7 @@ class BluesoundPlayer(MediaPlayerEntity):
if self._status is None: if self._status is None:
return MediaPlayerState.OFF return MediaPlayerState.OFF
if self.is_grouped and not self.is_master: if self.is_grouped and not self.is_leader:
return MediaPlayerState.IDLE return MediaPlayerState.IDLE
match self._status.state: match self._status.state:
@ -369,7 +409,7 @@ class BluesoundPlayer(MediaPlayerEntity):
@property @property
def media_title(self) -> str | None: def media_title(self) -> str | None:
"""Title of current playing media.""" """Title of current playing media."""
if self._status is None or (self.is_grouped and not self.is_master): if self._status is None or (self.is_grouped and not self.is_leader):
return None return None
return self._status.name return self._status.name
@ -380,7 +420,7 @@ class BluesoundPlayer(MediaPlayerEntity):
if self._status is None: if self._status is None:
return None return None
if self.is_grouped and not self.is_master: if self.is_grouped and not self.is_leader:
return self._group_name return self._group_name
return self._status.artist return self._status.artist
@ -388,7 +428,7 @@ class BluesoundPlayer(MediaPlayerEntity):
@property @property
def media_album_name(self) -> str | None: def media_album_name(self) -> str | None:
"""Artist of current playing media (Music track only).""" """Artist of current playing media (Music track only)."""
if self._status is None or (self.is_grouped and not self.is_master): if self._status is None or (self.is_grouped and not self.is_leader):
return None return None
return self._status.album return self._status.album
@ -396,7 +436,7 @@ class BluesoundPlayer(MediaPlayerEntity):
@property @property
def media_image_url(self) -> str | None: def media_image_url(self) -> str | None:
"""Image url of current playing media.""" """Image url of current playing media."""
if self._status is None or (self.is_grouped and not self.is_master): if self._status is None or (self.is_grouped and not self.is_leader):
return None return None
url = self._status.image url = self._status.image
@ -411,7 +451,7 @@ class BluesoundPlayer(MediaPlayerEntity):
@property @property
def media_position(self) -> int | None: def media_position(self) -> int | None:
"""Position of current playing media in seconds.""" """Position of current playing media in seconds."""
if self._status is None or (self.is_grouped and not self.is_master): if self._status is None or (self.is_grouped and not self.is_leader):
return None return None
mediastate = self.state mediastate = self.state
@ -430,7 +470,7 @@ class BluesoundPlayer(MediaPlayerEntity):
@property @property
def media_duration(self) -> int | None: def media_duration(self) -> int | None:
"""Duration of current playing media in seconds.""" """Duration of current playing media in seconds."""
if self._status is None or (self.is_grouped and not self.is_master): if self._status is None or (self.is_grouped and not self.is_leader):
return None return None
duration = self._status.total_seconds duration = self._status.total_seconds
@ -489,7 +529,7 @@ class BluesoundPlayer(MediaPlayerEntity):
@property @property
def source_list(self) -> list[str] | None: def source_list(self) -> list[str] | None:
"""List of available input sources.""" """List of available input sources."""
if self._status is None or (self.is_grouped and not self.is_master): if self._status is None or (self.is_grouped and not self.is_leader):
return None return None
sources = [x.text for x in self._inputs] sources = [x.text for x in self._inputs]
@ -500,7 +540,7 @@ class BluesoundPlayer(MediaPlayerEntity):
@property @property
def source(self) -> str | None: def source(self) -> str | None:
"""Name of the current input source.""" """Name of the current input source."""
if self._status is None or (self.is_grouped and not self.is_master): if self._status is None or (self.is_grouped and not self.is_leader):
return None return None
if self._status.input_id is not None: if self._status.input_id is not None:
@ -520,7 +560,7 @@ class BluesoundPlayer(MediaPlayerEntity):
if self._status is None: if self._status is None:
return MediaPlayerEntityFeature(0) return MediaPlayerEntityFeature(0)
if self.is_grouped and not self.is_master: if self.is_grouped and not self.is_leader:
return ( return (
MediaPlayerEntityFeature.VOLUME_STEP MediaPlayerEntityFeature.VOLUME_STEP
| MediaPlayerEntityFeature.VOLUME_SET | MediaPlayerEntityFeature.VOLUME_SET
@ -560,14 +600,17 @@ class BluesoundPlayer(MediaPlayerEntity):
return supported return supported
@property @property
def is_master(self) -> bool: def is_leader(self) -> bool:
"""Return true if player is a coordinator.""" """Return true if player is leader of a group."""
return self._is_master return self._sync_status.followers is not None
@property @property
def is_grouped(self) -> bool: def is_grouped(self) -> bool:
"""Return true if player is a coordinator.""" """Return true if player is member or leader of a group."""
return self._master is not None or self._is_master return (
self._sync_status.followers is not None
or self._sync_status.leader is not None
)
@property @property
def shuffle(self) -> bool: def shuffle(self) -> bool:
@ -580,25 +623,25 @@ class BluesoundPlayer(MediaPlayerEntity):
async def async_join(self, master: str) -> None: async def async_join(self, master: str) -> None:
"""Join the player to a group.""" """Join the player to a group."""
master_device = [ if master == self.entity_id:
device
for device in self.hass.data[DATA_BLUESOUND]
if device.entity_id == master
]
if len(master_device) > 0:
if self.id == master_device[0].id:
raise ServiceValidationError("Cannot join player to itself") raise ServiceValidationError("Cannot join player to itself")
_LOGGER.debug( _LOGGER.debug("Trying to join player: %s", self.id)
"Trying to join player: %s to master: %s", async_dispatcher_send(
self.id, self.hass, dispatcher_join_signal(master), self.host, self.port
master_device[0].id,
) )
await master_device[0].async_add_slave(self) async def async_unjoin(self) -> None:
else: """Unjoin the player from a group."""
_LOGGER.error("Master not found %s", master_device) if self._sync_status.leader is None:
return
leader_id = f"{self._sync_status.leader.ip}:{self._sync_status.leader.port}"
_LOGGER.debug("Trying to unjoin player: %s", self.id)
async_dispatcher_send(
self.hass, dispatcher_unjoin_signal(leader_id), self.host, self.port
)
@property @property
def extra_state_attributes(self) -> dict[str, Any] | None: def extra_state_attributes(self) -> dict[str, Any] | None:
@ -607,31 +650,31 @@ class BluesoundPlayer(MediaPlayerEntity):
if self._group_list: if self._group_list:
attributes = {ATTR_BLUESOUND_GROUP: self._group_list} attributes = {ATTR_BLUESOUND_GROUP: self._group_list}
attributes[ATTR_MASTER] = self._is_master attributes[ATTR_MASTER] = self.is_leader
return attributes return attributes
def rebuild_bluesound_group(self) -> list[str]: def rebuild_bluesound_group(self) -> list[str]:
"""Rebuild the list of entities in speaker group.""" """Rebuild the list of entities in speaker group."""
if self.sync_status.master is None and self.sync_status.slaves is None: if self.sync_status.leader is None and self.sync_status.followers is None:
return [] return []
player_entities: list[BluesoundPlayer] = self.hass.data[DATA_BLUESOUND] player_entities: list[BluesoundPlayer] = self.hass.data[DATA_BLUESOUND]
leader_sync_status: SyncStatus | None = None leader_sync_status: SyncStatus | None = None
if self.sync_status.master is None: if self.sync_status.leader is None:
leader_sync_status = self.sync_status leader_sync_status = self.sync_status
else: else:
required_id = f"{self.sync_status.master.ip}:{self.sync_status.master.port}" required_id = f"{self.sync_status.leader.ip}:{self.sync_status.leader.port}"
for x in player_entities: for x in player_entities:
if x.sync_status.id == required_id: if x.sync_status.id == required_id:
leader_sync_status = x.sync_status leader_sync_status = x.sync_status
break break
if leader_sync_status is None or leader_sync_status.slaves is None: if leader_sync_status is None or leader_sync_status.followers is None:
return [] return []
follower_ids = [f"{x.ip}:{x.port}" for x in leader_sync_status.slaves] follower_ids = [f"{x.ip}:{x.port}" for x in leader_sync_status.followers]
follower_names = [ follower_names = [
x.sync_status.name x.sync_status.name
for x in player_entities for x in player_entities
@ -640,21 +683,13 @@ class BluesoundPlayer(MediaPlayerEntity):
follower_names.insert(0, leader_sync_status.name) follower_names.insert(0, leader_sync_status.name)
return follower_names return follower_names
async def async_unjoin(self) -> None: async def async_add_follower(self, host: str, port: int) -> None:
"""Unjoin the player from a group.""" """Add follower to leader."""
if self._master is None: await self._player.add_follower(host, port)
return
_LOGGER.debug("Trying to unjoin player: %s", self.id) async def async_remove_follower(self, host: str, port: int) -> None:
await self._master.async_remove_slave(self) """Remove follower to leader."""
await self._player.remove_follower(host, port)
async def async_add_slave(self, slave_device: BluesoundPlayer) -> None:
"""Add slave to master."""
await self._player.add_slave(slave_device.host, slave_device.port)
async def async_remove_slave(self, slave_device: BluesoundPlayer) -> None:
"""Remove slave to master."""
await self._player.remove_slave(slave_device.host, slave_device.port)
async def async_increase_timer(self) -> int: async def async_increase_timer(self) -> int:
"""Increase sleep time on player.""" """Increase sleep time on player."""
@ -672,7 +707,7 @@ class BluesoundPlayer(MediaPlayerEntity):
async def async_select_source(self, source: str) -> None: async def async_select_source(self, source: str) -> None:
"""Select input source.""" """Select input source."""
if self.is_grouped and not self.is_master: if self.is_grouped and not self.is_leader:
return return
# presets and inputs might have the same name; presets have priority # presets and inputs might have the same name; presets have priority
@ -691,49 +726,49 @@ class BluesoundPlayer(MediaPlayerEntity):
async def async_clear_playlist(self) -> None: async def async_clear_playlist(self) -> None:
"""Clear players playlist.""" """Clear players playlist."""
if self.is_grouped and not self.is_master: if self.is_grouped and not self.is_leader:
return return
await self._player.clear() await self._player.clear()
async def async_media_next_track(self) -> None: async def async_media_next_track(self) -> None:
"""Send media_next command to media player.""" """Send media_next command to media player."""
if self.is_grouped and not self.is_master: if self.is_grouped and not self.is_leader:
return return
await self._player.skip() await self._player.skip()
async def async_media_previous_track(self) -> None: async def async_media_previous_track(self) -> None:
"""Send media_previous command to media player.""" """Send media_previous command to media player."""
if self.is_grouped and not self.is_master: if self.is_grouped and not self.is_leader:
return return
await self._player.back() await self._player.back()
async def async_media_play(self) -> None: async def async_media_play(self) -> None:
"""Send media_play command to media player.""" """Send media_play command to media player."""
if self.is_grouped and not self.is_master: if self.is_grouped and not self.is_leader:
return return
await self._player.play() await self._player.play()
async def async_media_pause(self) -> None: async def async_media_pause(self) -> None:
"""Send media_pause command to media player.""" """Send media_pause command to media player."""
if self.is_grouped and not self.is_master: if self.is_grouped and not self.is_leader:
return return
await self._player.pause() await self._player.pause()
async def async_media_stop(self) -> None: async def async_media_stop(self) -> None:
"""Send stop command.""" """Send stop command."""
if self.is_grouped and not self.is_master: if self.is_grouped and not self.is_leader:
return return
await self._player.stop() await self._player.stop()
async def async_media_seek(self, position: float) -> None: async def async_media_seek(self, position: float) -> None:
"""Send media_seek command to media player.""" """Send media_seek command to media player."""
if self.is_grouped and not self.is_master: if self.is_grouped and not self.is_leader:
return return
await self._player.play(seek=int(position)) await self._player.play(seek=int(position))
@ -742,7 +777,7 @@ class BluesoundPlayer(MediaPlayerEntity):
self, media_type: MediaType | str, media_id: str, **kwargs: Any self, media_type: MediaType | str, media_id: str, **kwargs: Any
) -> None: ) -> None:
"""Send the play_media command to the media player.""" """Send the play_media command to the media player."""
if self.is_grouped and not self.is_master: if self.is_grouped and not self.is_leader:
return return
if media_source.is_media_source_id(media_id): if media_source.is_media_source_id(media_id):

View File

@ -1,68 +0,0 @@
"""Support for Bluesound devices."""
from __future__ import annotations
from typing import NamedTuple
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
from .const import ATTR_MASTER, DOMAIN
SERVICE_CLEAR_TIMER = "clear_sleep_timer"
SERVICE_JOIN = "join"
SERVICE_SET_TIMER = "set_sleep_timer"
SERVICE_UNJOIN = "unjoin"
BS_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids})
BS_JOIN_SCHEMA = BS_SCHEMA.extend({vol.Required(ATTR_MASTER): cv.entity_id})
class ServiceMethodDetails(NamedTuple):
"""Details for SERVICE_TO_METHOD mapping."""
method: str
schema: vol.Schema
SERVICE_TO_METHOD = {
SERVICE_JOIN: ServiceMethodDetails(method="async_join", schema=BS_JOIN_SCHEMA),
SERVICE_UNJOIN: ServiceMethodDetails(method="async_unjoin", schema=BS_SCHEMA),
SERVICE_SET_TIMER: ServiceMethodDetails(
method="async_increase_timer", schema=BS_SCHEMA
),
SERVICE_CLEAR_TIMER: ServiceMethodDetails(
method="async_clear_timer", schema=BS_SCHEMA
),
}
def setup_services(hass: HomeAssistant) -> None:
"""Set up services for Bluesound component."""
async def async_service_handler(service: ServiceCall) -> None:
"""Map services to method of Bluesound devices."""
if not (method := SERVICE_TO_METHOD.get(service.service)):
return
params = {
key: value for key, value in service.data.items() if key != ATTR_ENTITY_ID
}
if entity_ids := service.data.get(ATTR_ENTITY_ID):
target_players = [
player for player in hass.data[DOMAIN] if player.entity_id in entity_ids
]
else:
target_players = hass.data[DOMAIN]
for player in target_players:
await getattr(player, method.method)(**params)
for service, method in SERVICE_TO_METHOD.items():
hass.services.async_register(
DOMAIN, service, async_service_handler, schema=method.schema
)

View File

@ -6,3 +6,16 @@ from homeassistant.helpers.device_registry import format_mac
def format_unique_id(mac: str, port: int) -> str: def format_unique_id(mac: str, port: int) -> str:
"""Generate a unique ID based on the MAC address and port number.""" """Generate a unique ID based on the MAC address and port number."""
return f"{format_mac(mac)}-{port}" return f"{format_mac(mac)}-{port}"
def dispatcher_join_signal(entity_id: str) -> str:
"""Join an entity ID with a signal."""
return f"bluesound_join_{entity_id}"
def dispatcher_unjoin_signal(leader_id: str) -> str:
"""Unjoin an entity ID with a signal.
Id is ip_address:port. This can be obtained from sync_status.id.
"""
return f"bluesound_unjoin_{leader_id}"

View File

@ -1803,7 +1803,7 @@ pybbox==0.0.5-alpha
pyblackbird==0.6 pyblackbird==0.6
# homeassistant.components.bluesound # homeassistant.components.bluesound
pyblu==1.0.4 pyblu==2.0.0
# homeassistant.components.neato # homeassistant.components.neato
pybotvac==0.0.25 pybotvac==0.0.25

View File

@ -1477,7 +1477,7 @@ pybalboa==1.0.2
pyblackbird==0.6 pyblackbird==0.6
# homeassistant.components.bluesound # homeassistant.components.bluesound
pyblu==1.0.4 pyblu==2.0.0
# homeassistant.components.neato # homeassistant.components.neato
pybotvac==0.0.25 pybotvac==0.0.25

View File

@ -81,11 +81,11 @@ class PlayerMockData:
volume_db=0.5, volume_db=0.5,
volume=50, volume=50,
group=None, group=None,
master=None, leader=None,
slaves=None, followers=None,
zone=None, zone=None,
zone_master=None, zone_leader=None,
zone_slave=None, zone_follower=None,
mute_volume_db=None, mute_volume_db=None,
mute_volume=None, mute_volume=None,
) )

View File

@ -11,7 +11,7 @@ from syrupy.filters import props
from homeassistant.components.bluesound import DOMAIN as BLUESOUND_DOMAIN from homeassistant.components.bluesound import DOMAIN as BLUESOUND_DOMAIN
from homeassistant.components.bluesound.const import ATTR_MASTER from homeassistant.components.bluesound.const import ATTR_MASTER
from homeassistant.components.bluesound.services import ( from homeassistant.components.bluesound.media_player import (
SERVICE_CLEAR_TIMER, SERVICE_CLEAR_TIMER,
SERVICE_JOIN, SERVICE_JOIN,
SERVICE_SET_TIMER, SERVICE_SET_TIMER,
@ -259,7 +259,7 @@ async def test_join(
blocking=True, blocking=True,
) )
player_mocks.player_data_secondary.player.add_slave.assert_called_once_with( player_mocks.player_data_secondary.player.add_follower.assert_called_once_with(
"1.1.1.1", 11000 "1.1.1.1", 11000
) )
@ -273,7 +273,7 @@ async def test_unjoin(
"""Test the unjoin action.""" """Test the unjoin action."""
updated_sync_status = dataclasses.replace( updated_sync_status = dataclasses.replace(
player_mocks.player_data.sync_status_long_polling_mock.get(), player_mocks.player_data.sync_status_long_polling_mock.get(),
master=PairedPlayer("2.2.2.2", 11000), leader=PairedPlayer("2.2.2.2", 11000),
) )
player_mocks.player_data.sync_status_long_polling_mock.set(updated_sync_status) player_mocks.player_data.sync_status_long_polling_mock.set(updated_sync_status)
@ -287,7 +287,7 @@ async def test_unjoin(
blocking=True, blocking=True,
) )
player_mocks.player_data_secondary.player.remove_slave.assert_called_once_with( player_mocks.player_data_secondary.player.remove_follower.assert_called_once_with(
"1.1.1.1", 11000 "1.1.1.1", 11000
) )
@ -297,7 +297,7 @@ async def test_attr_master(
setup_config_entry: None, setup_config_entry: None,
player_mocks: PlayerMocks, player_mocks: PlayerMocks,
) -> None: ) -> None:
"""Test the media player master.""" """Test the media player leader."""
attr_master = hass.states.get("media_player.player_name1111").attributes[ attr_master = hass.states.get("media_player.player_name1111").attributes[
ATTR_MASTER ATTR_MASTER
] ]
@ -305,7 +305,7 @@ async def test_attr_master(
updated_sync_status = dataclasses.replace( updated_sync_status = dataclasses.replace(
player_mocks.player_data.sync_status_long_polling_mock.get(), player_mocks.player_data.sync_status_long_polling_mock.get(),
slaves=[PairedPlayer("2.2.2.2", 11000)], followers=[PairedPlayer("2.2.2.2", 11000)],
) )
player_mocks.player_data.sync_status_long_polling_mock.set(updated_sync_status) player_mocks.player_data.sync_status_long_polling_mock.set(updated_sync_status)
@ -333,7 +333,7 @@ async def test_attr_bluesound_group(
updated_sync_status = dataclasses.replace( updated_sync_status = dataclasses.replace(
player_mocks.player_data.sync_status_long_polling_mock.get(), player_mocks.player_data.sync_status_long_polling_mock.get(),
slaves=[PairedPlayer("2.2.2.2", 11000)], followers=[PairedPlayer("2.2.2.2", 11000)],
) )
player_mocks.player_data.sync_status_long_polling_mock.set(updated_sync_status) player_mocks.player_data.sync_status_long_polling_mock.set(updated_sync_status)
@ -361,7 +361,7 @@ async def test_attr_bluesound_group_for_follower(
updated_sync_status = dataclasses.replace( updated_sync_status = dataclasses.replace(
player_mocks.player_data.sync_status_long_polling_mock.get(), player_mocks.player_data.sync_status_long_polling_mock.get(),
slaves=[PairedPlayer("2.2.2.2", 11000)], followers=[PairedPlayer("2.2.2.2", 11000)],
) )
player_mocks.player_data.sync_status_long_polling_mock.set(updated_sync_status) player_mocks.player_data.sync_status_long_polling_mock.set(updated_sync_status)
@ -370,7 +370,7 @@ async def test_attr_bluesound_group_for_follower(
updated_sync_status = dataclasses.replace( updated_sync_status = dataclasses.replace(
player_mocks.player_data_secondary.sync_status_long_polling_mock.get(), player_mocks.player_data_secondary.sync_status_long_polling_mock.get(),
master=PairedPlayer("1.1.1.1", 11000), leader=PairedPlayer("1.1.1.1", 11000),
) )
player_mocks.player_data_secondary.sync_status_long_polling_mock.set( player_mocks.player_data_secondary.sync_status_long_polling_mock.set(
updated_sync_status updated_sync_status