mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Remove mapping of entity_ids to speakers in Sonos (#147506)
* fix * fix: change entity_id mappings * fix: translate errors * fix:merge issues * fix: translate error messages * fix: improve test coverage * fix: remove unneeded strings
This commit is contained in:
parent
c05d8aab1c
commit
d8258924f7
@ -6,6 +6,7 @@ import time
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
|
||||
from .const import DOMAIN
|
||||
@ -132,11 +133,23 @@ async def async_generate_speaker_info(
|
||||
value = getattr(speaker, attrib)
|
||||
payload[attrib] = get_contents(value)
|
||||
|
||||
entity_registry = er.async_get(hass)
|
||||
payload["enabled_entities"] = sorted(
|
||||
entity_id
|
||||
for entity_id, s in config_entry.runtime_data.entity_id_mappings.items()
|
||||
if s is speaker
|
||||
registry_entry.entity_id
|
||||
for registry_entry in entity_registry.entities.get_entries_for_config_entry_id(
|
||||
config_entry.entry_id
|
||||
)
|
||||
if (
|
||||
(
|
||||
entity_speaker
|
||||
:= config_entry.runtime_data.unique_id_speaker_mappings.get(
|
||||
registry_entry.unique_id
|
||||
)
|
||||
)
|
||||
and speaker.uid == entity_speaker.uid
|
||||
)
|
||||
)
|
||||
|
||||
payload["media"] = await async_generate_media_info(hass, speaker)
|
||||
payload["activity_stats"] = speaker.activity_stats.report()
|
||||
payload["event_stats"] = speaker.event_stats.report()
|
||||
|
@ -34,7 +34,10 @@ class SonosEntity(Entity):
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Handle common setup when added to hass."""
|
||||
self.config_entry.runtime_data.entity_id_mappings[self.entity_id] = self.speaker
|
||||
assert self.unique_id
|
||||
self.config_entry.runtime_data.unique_id_speaker_mappings[self.unique_id] = (
|
||||
self.speaker
|
||||
)
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
@ -52,7 +55,8 @@ class SonosEntity(Entity):
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Clean up when entity is removed."""
|
||||
del self.config_entry.runtime_data.entity_id_mappings[self.entity_id]
|
||||
assert self.unique_id
|
||||
del self.config_entry.runtime_data.unique_id_speaker_mappings[self.unique_id]
|
||||
|
||||
async def async_fallback_poll(self, now: datetime.datetime) -> None:
|
||||
"""Poll the entity if subscriptions fail."""
|
||||
|
@ -149,7 +149,8 @@ class SonosData:
|
||||
discovery_known: set[str] = field(default_factory=set)
|
||||
boot_counts: dict[str, int] = field(default_factory=dict)
|
||||
mdns_names: dict[str, str] = field(default_factory=dict)
|
||||
entity_id_mappings: dict[str, SonosSpeaker] = field(default_factory=dict)
|
||||
# Maps the entity unique id to the associated SonosSpeaker instance.
|
||||
unique_id_speaker_mappings: dict[str, SonosSpeaker] = field(default_factory=dict)
|
||||
unjoin_data: dict[str, UnjoinData] = field(default_factory=dict)
|
||||
|
||||
|
||||
|
@ -43,7 +43,12 @@ from homeassistant.components.plex.services import process_plex_payload
|
||||
from homeassistant.const import ATTR_TIME
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse, callback
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform, service
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
entity_platform,
|
||||
entity_registry as er,
|
||||
service,
|
||||
)
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
@ -880,13 +885,28 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
||||
async def async_join_players(self, group_members: list[str]) -> None:
|
||||
"""Join `group_members` as a player group with the current player."""
|
||||
speakers = []
|
||||
|
||||
entity_registry = er.async_get(self.hass)
|
||||
for entity_id in group_members:
|
||||
if speaker := self.config_entry.runtime_data.entity_id_mappings.get(
|
||||
entity_id
|
||||
if not (entity_reg_entry := entity_registry.async_get(entity_id)):
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="entity_not_found",
|
||||
translation_placeholders={"entity_id": entity_id},
|
||||
)
|
||||
if not (
|
||||
speaker
|
||||
:= self.config_entry.runtime_data.unique_id_speaker_mappings.get(
|
||||
entity_reg_entry.unique_id
|
||||
)
|
||||
):
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="speaker_not_found",
|
||||
translation_placeholders={"entity_id": entity_id},
|
||||
)
|
||||
|
||||
speakers.append(speaker)
|
||||
else:
|
||||
raise HomeAssistantError(f"Not a known Sonos entity_id: {entity_id}")
|
||||
|
||||
await SonosSpeaker.join_multi(
|
||||
self.hass, self.config_entry, self.speaker, speakers
|
||||
|
@ -195,6 +195,12 @@
|
||||
"announce_media_error": {
|
||||
"message": "Announcing clip {media_id} failed {response}"
|
||||
},
|
||||
"entity_not_found": {
|
||||
"message": "Entity {entity_id} not found."
|
||||
},
|
||||
"speaker_not_found": {
|
||||
"message": "{entity_id} is not a known Sonos speaker."
|
||||
},
|
||||
"timeout_join": {
|
||||
"message": "Timeout while waiting for Sonos player to join the group {group_description}"
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ from homeassistant.components.media_player import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .conftest import MockSoCo, group_speakers, ungroup_speakers
|
||||
|
||||
@ -85,6 +86,31 @@ async def test_media_player_join_bad_entity(
|
||||
assert "media_player.bad_entity" in str(excinfo.value)
|
||||
|
||||
|
||||
async def test_media_player_join_entity_no_speaker(
|
||||
hass: HomeAssistant,
|
||||
sonos_setup_two_speakers: list[MockSoCo],
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test error handling of joining with no associated speaker."""
|
||||
|
||||
bad_media_player = entity_registry.async_get_or_create(
|
||||
"media_player", "demo", "1234"
|
||||
)
|
||||
|
||||
# Ensure an error is raised if the entity does not have a speaker
|
||||
with pytest.raises(HomeAssistantError) as excinfo:
|
||||
await hass.services.async_call(
|
||||
MP_DOMAIN,
|
||||
SERVICE_JOIN,
|
||||
{
|
||||
"entity_id": "media_player.living_room",
|
||||
"group_members": bad_media_player.entity_id,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert bad_media_player.entity_id in str(excinfo.value)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def instant_timeout(*args, **kwargs) -> None:
|
||||
"""Mock a timeout error."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user