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 typing import Any
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.helpers.device_registry import DeviceEntry
|
from homeassistant.helpers.device_registry import DeviceEntry
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
@ -132,11 +133,23 @@ async def async_generate_speaker_info(
|
|||||||
value = getattr(speaker, attrib)
|
value = getattr(speaker, attrib)
|
||||||
payload[attrib] = get_contents(value)
|
payload[attrib] = get_contents(value)
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
payload["enabled_entities"] = sorted(
|
payload["enabled_entities"] = sorted(
|
||||||
entity_id
|
registry_entry.entity_id
|
||||||
for entity_id, s in config_entry.runtime_data.entity_id_mappings.items()
|
for registry_entry in entity_registry.entities.get_entries_for_config_entry_id(
|
||||||
if s is speaker
|
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["media"] = await async_generate_media_info(hass, speaker)
|
||||||
payload["activity_stats"] = speaker.activity_stats.report()
|
payload["activity_stats"] = speaker.activity_stats.report()
|
||||||
payload["event_stats"] = speaker.event_stats.report()
|
payload["event_stats"] = speaker.event_stats.report()
|
||||||
|
@ -34,7 +34,10 @@ class SonosEntity(Entity):
|
|||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Handle common setup when added to hass."""
|
"""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(
|
self.async_on_remove(
|
||||||
async_dispatcher_connect(
|
async_dispatcher_connect(
|
||||||
self.hass,
|
self.hass,
|
||||||
@ -52,7 +55,8 @@ class SonosEntity(Entity):
|
|||||||
|
|
||||||
async def async_will_remove_from_hass(self) -> None:
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
"""Clean up when entity is removed."""
|
"""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:
|
async def async_fallback_poll(self, now: datetime.datetime) -> None:
|
||||||
"""Poll the entity if subscriptions fail."""
|
"""Poll the entity if subscriptions fail."""
|
||||||
|
@ -149,7 +149,8 @@ class SonosData:
|
|||||||
discovery_known: set[str] = field(default_factory=set)
|
discovery_known: set[str] = field(default_factory=set)
|
||||||
boot_counts: dict[str, int] = field(default_factory=dict)
|
boot_counts: dict[str, int] = field(default_factory=dict)
|
||||||
mdns_names: dict[str, str] = 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)
|
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.const import ATTR_TIME
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse, callback
|
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
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.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
from homeassistant.helpers.event import async_call_later
|
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:
|
async def async_join_players(self, group_members: list[str]) -> None:
|
||||||
"""Join `group_members` as a player group with the current player."""
|
"""Join `group_members` as a player group with the current player."""
|
||||||
speakers = []
|
speakers = []
|
||||||
|
|
||||||
|
entity_registry = er.async_get(self.hass)
|
||||||
for entity_id in group_members:
|
for entity_id in group_members:
|
||||||
if speaker := self.config_entry.runtime_data.entity_id_mappings.get(
|
if not (entity_reg_entry := entity_registry.async_get(entity_id)):
|
||||||
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
|
||||||
|
)
|
||||||
):
|
):
|
||||||
speakers.append(speaker)
|
raise HomeAssistantError(
|
||||||
else:
|
translation_domain=DOMAIN,
|
||||||
raise HomeAssistantError(f"Not a known Sonos entity_id: {entity_id}")
|
translation_key="speaker_not_found",
|
||||||
|
translation_placeholders={"entity_id": entity_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
speakers.append(speaker)
|
||||||
|
|
||||||
await SonosSpeaker.join_multi(
|
await SonosSpeaker.join_multi(
|
||||||
self.hass, self.config_entry, self.speaker, speakers
|
self.hass, self.config_entry, self.speaker, speakers
|
||||||
|
@ -195,6 +195,12 @@
|
|||||||
"announce_media_error": {
|
"announce_media_error": {
|
||||||
"message": "Announcing clip {media_id} failed {response}"
|
"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": {
|
"timeout_join": {
|
||||||
"message": "Timeout while waiting for Sonos player to join the group {group_description}"
|
"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.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from .conftest import MockSoCo, group_speakers, ungroup_speakers
|
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)
|
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
|
@asynccontextmanager
|
||||||
async def instant_timeout(*args, **kwargs) -> None:
|
async def instant_timeout(*args, **kwargs) -> None:
|
||||||
"""Mock a timeout error."""
|
"""Mock a timeout error."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user