Files
core/homeassistant/components/sonos/select.py
2025-09-30 20:25:19 +02:00

133 lines
4.4 KiB
Python

"""Select entities for Sonos."""
from __future__ import annotations
from dataclasses import dataclass
import logging
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import (
ATTR_DIALOG_LEVEL,
ATTR_DIALOG_LEVEL_ENUM,
MODEL_SONOS_ARC_ULTRA,
SONOS_CREATE_SELECTS,
SPEECH_DIALOG_LEVEL,
)
from .entity import SonosEntity
from .helpers import SonosConfigEntry, soco_error
from .speaker import SonosSpeaker
@dataclass(frozen=True, kw_only=True)
class SonosSelectEntityDescription(SelectEntityDescription):
"""Describes AirGradient select entity."""
soco_attribute: str
speaker_attribute: str
speaker_model: str
SELECT_TYPES: list[SonosSelectEntityDescription] = [
SonosSelectEntityDescription(
key=SPEECH_DIALOG_LEVEL,
translation_key=SPEECH_DIALOG_LEVEL,
soco_attribute=ATTR_DIALOG_LEVEL,
speaker_attribute=ATTR_DIALOG_LEVEL_ENUM,
speaker_model=MODEL_SONOS_ARC_ULTRA,
options=["off", "low", "medium", "high", "max"],
),
]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: SonosConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Sonos select platform from a config entry."""
def available_soco_attributes(
speaker: SonosSpeaker,
) -> list[SonosSelectEntityDescription]:
features: list[SonosSelectEntityDescription] = []
for select_data in SELECT_TYPES:
if select_data.speaker_model == speaker.model_name.upper():
if (
speaker.update_soco_int_attribute(
select_data.soco_attribute, select_data.speaker_attribute
)
is not None
):
features.append(select_data)
return features
async def _async_create_entities(speaker: SonosSpeaker) -> None:
available_features = await hass.async_add_executor_job(
available_soco_attributes, speaker
)
async_add_entities(
SonosSelectEntity(speaker, config_entry, select_data)
for select_data in available_features
)
config_entry.async_on_unload(
async_dispatcher_connect(hass, SONOS_CREATE_SELECTS, _async_create_entities)
)
class SonosSelectEntity(SonosEntity, SelectEntity):
"""Representation of a Sonos select entity."""
def __init__(
self,
speaker: SonosSpeaker,
config_entry: SonosConfigEntry,
select_data: SonosSelectEntityDescription,
) -> None:
"""Initialize the select entity."""
super().__init__(speaker, config_entry)
self._attr_unique_id = f"{self.soco.uid}-{select_data.key}"
self._attr_translation_key = select_data.translation_key
assert select_data.options is not None
self._attr_options = select_data.options
self.speaker_attribute = select_data.speaker_attribute
self.soco_attribute = select_data.soco_attribute
async def _async_fallback_poll(self) -> None:
"""Poll the value if subscriptions are not working."""
await self.hass.async_add_executor_job(self.poll_state)
self.async_write_ha_state()
@soco_error()
def poll_state(self) -> None:
"""Poll the device for the current state."""
self.speaker.update_soco_int_attribute(
self.soco_attribute, self.speaker_attribute
)
@property
def current_option(self) -> str | None:
"""Return the current option for the entity."""
option = getattr(self.speaker, self.speaker_attribute, None)
if not isinstance(option, int) or not (0 <= option < len(self._attr_options)):
_LOGGER.error(
"Invalid option %s for %s on %s",
option,
self.soco_attribute,
self.speaker.zone_name,
)
return None
return self._attr_options[option]
@soco_error()
def select_option(self, option: str) -> None:
"""Set a new value."""
dialog_level = self._attr_options.index(option)
setattr(self.soco, self.soco_attribute, dialog_level)