mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Move Sonos bass & treble controls to number entities (#60498)
This commit is contained in:
parent
3aa35e15c2
commit
a88cc8b98c
@ -19,6 +19,7 @@ from homeassistant.components.media_player.const import (
|
|||||||
MEDIA_TYPE_PLAYLIST,
|
MEDIA_TYPE_PLAYLIST,
|
||||||
MEDIA_TYPE_TRACK,
|
MEDIA_TYPE_TRACK,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
|
||||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||||
|
|
||||||
@ -27,7 +28,13 @@ UPNP_ST = "urn:schemas-upnp-org:device:ZonePlayer:1"
|
|||||||
DOMAIN = "sonos"
|
DOMAIN = "sonos"
|
||||||
DATA_SONOS = "sonos_media_player"
|
DATA_SONOS = "sonos_media_player"
|
||||||
DATA_SONOS_DISCOVERY_MANAGER = "sonos_discovery_manager"
|
DATA_SONOS_DISCOVERY_MANAGER = "sonos_discovery_manager"
|
||||||
PLATFORMS = {BINARY_SENSOR_DOMAIN, MP_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN}
|
PLATFORMS = {
|
||||||
|
BINARY_SENSOR_DOMAIN,
|
||||||
|
MP_DOMAIN,
|
||||||
|
NUMBER_DOMAIN,
|
||||||
|
SENSOR_DOMAIN,
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
}
|
||||||
|
|
||||||
SONOS_ARTIST = "artists"
|
SONOS_ARTIST = "artists"
|
||||||
SONOS_ALBUM = "albums"
|
SONOS_ALBUM = "albums"
|
||||||
@ -139,6 +146,7 @@ SONOS_CHECK_ACTIVITY = "sonos_check_activity"
|
|||||||
SONOS_CREATE_ALARM = "sonos_create_alarm"
|
SONOS_CREATE_ALARM = "sonos_create_alarm"
|
||||||
SONOS_CREATE_BATTERY = "sonos_create_battery"
|
SONOS_CREATE_BATTERY = "sonos_create_battery"
|
||||||
SONOS_CREATE_SWITCHES = "sonos_create_switches"
|
SONOS_CREATE_SWITCHES = "sonos_create_switches"
|
||||||
|
SONOS_CREATE_LEVELS = "sonos_create_levels"
|
||||||
SONOS_CREATE_MEDIA_PLAYER = "sonos_create_media_player"
|
SONOS_CREATE_MEDIA_PLAYER = "sonos_create_media_player"
|
||||||
SONOS_ENTITY_CREATED = "sonos_entity_created"
|
SONOS_ENTITY_CREATED = "sonos_entity_created"
|
||||||
SONOS_POLL_UPDATE = "sonos_poll_update"
|
SONOS_POLL_UPDATE = "sonos_poll_update"
|
||||||
|
@ -108,7 +108,6 @@ SERVICE_RESTORE = "restore"
|
|||||||
SERVICE_SET_TIMER = "set_sleep_timer"
|
SERVICE_SET_TIMER = "set_sleep_timer"
|
||||||
SERVICE_CLEAR_TIMER = "clear_sleep_timer"
|
SERVICE_CLEAR_TIMER = "clear_sleep_timer"
|
||||||
SERVICE_UPDATE_ALARM = "update_alarm"
|
SERVICE_UPDATE_ALARM = "update_alarm"
|
||||||
SERVICE_SET_OPTION = "set_option"
|
|
||||||
SERVICE_PLAY_QUEUE = "play_queue"
|
SERVICE_PLAY_QUEUE = "play_queue"
|
||||||
SERVICE_REMOVE_FROM_QUEUE = "remove_from_queue"
|
SERVICE_REMOVE_FROM_QUEUE = "remove_from_queue"
|
||||||
|
|
||||||
@ -120,8 +119,6 @@ ATTR_INCLUDE_LINKED_ZONES = "include_linked_zones"
|
|||||||
ATTR_MASTER = "master"
|
ATTR_MASTER = "master"
|
||||||
ATTR_WITH_GROUP = "with_group"
|
ATTR_WITH_GROUP = "with_group"
|
||||||
ATTR_QUEUE_POSITION = "queue_position"
|
ATTR_QUEUE_POSITION = "queue_position"
|
||||||
ATTR_EQ_BASS = "bass_level"
|
|
||||||
ATTR_EQ_TREBLE = "treble_level"
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@ -225,19 +222,6 @@ async def async_setup_entry(
|
|||||||
"set_alarm",
|
"set_alarm",
|
||||||
)
|
)
|
||||||
|
|
||||||
platform.async_register_entity_service( # type: ignore
|
|
||||||
SERVICE_SET_OPTION,
|
|
||||||
{
|
|
||||||
vol.Optional(ATTR_EQ_BASS): vol.All(
|
|
||||||
vol.Coerce(int), vol.Range(min=-10, max=10)
|
|
||||||
),
|
|
||||||
vol.Optional(ATTR_EQ_TREBLE): vol.All(
|
|
||||||
vol.Coerce(int), vol.Range(min=-10, max=10)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
"set_option",
|
|
||||||
)
|
|
||||||
|
|
||||||
platform.async_register_entity_service( # type: ignore
|
platform.async_register_entity_service( # type: ignore
|
||||||
SERVICE_PLAY_QUEUE,
|
SERVICE_PLAY_QUEUE,
|
||||||
{vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int},
|
{vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int},
|
||||||
@ -605,19 +589,6 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
|||||||
alarm.include_linked_zones = include_linked_zones
|
alarm.include_linked_zones = include_linked_zones
|
||||||
alarm.save()
|
alarm.save()
|
||||||
|
|
||||||
@soco_error()
|
|
||||||
def set_option(
|
|
||||||
self,
|
|
||||||
bass_level: int | None = None,
|
|
||||||
treble_level: int | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""Modify playback options."""
|
|
||||||
if bass_level is not None:
|
|
||||||
self.soco.bass = bass_level
|
|
||||||
|
|
||||||
if treble_level is not None:
|
|
||||||
self.soco.treble = treble_level
|
|
||||||
|
|
||||||
@soco_error()
|
@soco_error()
|
||||||
def play_queue(self, queue_position: int = 0) -> None:
|
def play_queue(self, queue_position: int = 0) -> None:
|
||||||
"""Start playing the queue."""
|
"""Start playing the queue."""
|
||||||
@ -635,12 +606,6 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
|
|||||||
ATTR_SONOS_GROUP: self.speaker.sonos_group_entities
|
ATTR_SONOS_GROUP: self.speaker.sonos_group_entities
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.speaker.bass_level is not None:
|
|
||||||
attributes[ATTR_EQ_BASS] = self.speaker.bass_level
|
|
||||||
|
|
||||||
if self.speaker.treble_level is not None:
|
|
||||||
attributes[ATTR_EQ_TREBLE] = self.speaker.treble_level
|
|
||||||
|
|
||||||
if self.media.queue_position is not None:
|
if self.media.queue_position is not None:
|
||||||
attributes[ATTR_QUEUE_POSITION] = self.media.queue_position
|
attributes[ATTR_QUEUE_POSITION] = self.media.queue_position
|
||||||
|
|
||||||
|
64
homeassistant/components/sonos/number.py
Normal file
64
homeassistant/components/sonos/number.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
"""Entity representing a Sonos number control."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.components.number import NumberEntity
|
||||||
|
from homeassistant.const import ENTITY_CATEGORY_CONFIG
|
||||||
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
|
|
||||||
|
from .const import SONOS_CREATE_LEVELS
|
||||||
|
from .entity import SonosEntity
|
||||||
|
from .helpers import soco_error
|
||||||
|
from .speaker import SonosSpeaker
|
||||||
|
|
||||||
|
LEVEL_TYPES = ("bass", "treble")
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
|
"""Set up the Sonos number platform from a config entry."""
|
||||||
|
|
||||||
|
async def _async_create_entities(speaker: SonosSpeaker) -> None:
|
||||||
|
entities = []
|
||||||
|
for level_type in LEVEL_TYPES:
|
||||||
|
entities.append(SonosLevelEntity(speaker, level_type))
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
config_entry.async_on_unload(
|
||||||
|
async_dispatcher_connect(hass, SONOS_CREATE_LEVELS, _async_create_entities)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SonosLevelEntity(SonosEntity, NumberEntity):
|
||||||
|
"""Representation of a Sonos level entity."""
|
||||||
|
|
||||||
|
_attr_entity_category = ENTITY_CATEGORY_CONFIG
|
||||||
|
_attr_min_value = -10
|
||||||
|
_attr_max_value = 10
|
||||||
|
|
||||||
|
def __init__(self, speaker: SonosSpeaker, level_type: str) -> None:
|
||||||
|
"""Initialize the level entity."""
|
||||||
|
super().__init__(speaker)
|
||||||
|
self.level_type = level_type
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Return the unique ID."""
|
||||||
|
return f"{self.soco.uid}-{self.level_type}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
"""Return the name."""
|
||||||
|
return f"{self.speaker.zone_name} {self.level_type.capitalize()}"
|
||||||
|
|
||||||
|
async def _async_poll(self) -> None:
|
||||||
|
"""Poll the value if subscriptions are not working."""
|
||||||
|
# Handled by SonosSpeaker
|
||||||
|
|
||||||
|
@soco_error()
|
||||||
|
def set_value(self, value: float) -> None:
|
||||||
|
"""Set a new value."""
|
||||||
|
setattr(self.soco, self.level_type, value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self) -> float:
|
||||||
|
"""Return the current value."""
|
||||||
|
return getattr(self.speaker, self.level_type)
|
@ -87,30 +87,6 @@ clear_sleep_timer:
|
|||||||
device:
|
device:
|
||||||
integration: sonos
|
integration: sonos
|
||||||
|
|
||||||
set_option:
|
|
||||||
name: Set option
|
|
||||||
description: Set Sonos sound options.
|
|
||||||
target:
|
|
||||||
device:
|
|
||||||
integration: sonos
|
|
||||||
fields:
|
|
||||||
bass_level:
|
|
||||||
name: Bass Level
|
|
||||||
description: Bass level for EQ.
|
|
||||||
selector:
|
|
||||||
number:
|
|
||||||
min: -10
|
|
||||||
max: 10
|
|
||||||
mode: box
|
|
||||||
treble_level:
|
|
||||||
name: Treble Level
|
|
||||||
description: Treble level for EQ.
|
|
||||||
selector:
|
|
||||||
number:
|
|
||||||
min: -10
|
|
||||||
max: 10
|
|
||||||
mode: box
|
|
||||||
|
|
||||||
play_queue:
|
play_queue:
|
||||||
name: Play queue
|
name: Play queue
|
||||||
description: Start playing the queue from the first item.
|
description: Start playing the queue from the first item.
|
||||||
|
@ -46,6 +46,7 @@ from .const import (
|
|||||||
SONOS_CHECK_ACTIVITY,
|
SONOS_CHECK_ACTIVITY,
|
||||||
SONOS_CREATE_ALARM,
|
SONOS_CREATE_ALARM,
|
||||||
SONOS_CREATE_BATTERY,
|
SONOS_CREATE_BATTERY,
|
||||||
|
SONOS_CREATE_LEVELS,
|
||||||
SONOS_CREATE_MEDIA_PLAYER,
|
SONOS_CREATE_MEDIA_PLAYER,
|
||||||
SONOS_CREATE_SWITCHES,
|
SONOS_CREATE_SWITCHES,
|
||||||
SONOS_ENTITY_CREATED,
|
SONOS_ENTITY_CREATED,
|
||||||
@ -192,8 +193,8 @@ class SonosSpeaker:
|
|||||||
self.night_mode: bool | None = None
|
self.night_mode: bool | None = None
|
||||||
self.dialog_mode: bool | None = None
|
self.dialog_mode: bool | None = None
|
||||||
self.cross_fade: bool | None = None
|
self.cross_fade: bool | None = None
|
||||||
self.bass_level: int | None = None
|
self.bass: int | None = None
|
||||||
self.treble_level: int | None = None
|
self.treble: int | None = None
|
||||||
|
|
||||||
# Misc features
|
# Misc features
|
||||||
self.buttons_enabled: bool | None = None
|
self.buttons_enabled: bool | None = None
|
||||||
@ -234,6 +235,8 @@ class SonosSpeaker:
|
|||||||
)
|
)
|
||||||
future.result(timeout=10)
|
future.result(timeout=10)
|
||||||
|
|
||||||
|
dispatcher_send(self.hass, SONOS_CREATE_LEVELS, self)
|
||||||
|
|
||||||
if battery_info := fetch_battery_info_or_none(self.soco):
|
if battery_info := fetch_battery_info_or_none(self.soco):
|
||||||
self.battery_info = battery_info
|
self.battery_info = battery_info
|
||||||
# Battery events can be infrequent, polling is still necessary
|
# Battery events can be infrequent, polling is still necessary
|
||||||
@ -490,11 +493,11 @@ class SonosSpeaker:
|
|||||||
if "dialog_level" in variables:
|
if "dialog_level" in variables:
|
||||||
self.dialog_mode = variables["dialog_level"] == "1"
|
self.dialog_mode = variables["dialog_level"] == "1"
|
||||||
|
|
||||||
if "bass_level" in variables:
|
if "bass" in variables:
|
||||||
self.bass_level = variables["bass_level"]
|
self.bass = variables["bass"]
|
||||||
|
|
||||||
if "treble_level" in variables:
|
if "treble" in variables:
|
||||||
self.treble_level = variables["treble_level"]
|
self.treble = variables["treble"]
|
||||||
|
|
||||||
self.async_write_entity_states()
|
self.async_write_entity_states()
|
||||||
|
|
||||||
@ -968,8 +971,8 @@ class SonosSpeaker:
|
|||||||
self.muted = self.soco.mute
|
self.muted = self.soco.mute
|
||||||
self.night_mode = self.soco.night_mode
|
self.night_mode = self.soco.night_mode
|
||||||
self.dialog_mode = self.soco.dialog_mode
|
self.dialog_mode = self.soco.dialog_mode
|
||||||
self.bass_level = self.soco.bass
|
self.bass = self.soco.bass
|
||||||
self.treble_level = self.soco.treble
|
self.treble = self.soco.treble
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.cross_fade = self.soco.cross_fade
|
self.cross_fade = self.soco.cross_fade
|
||||||
|
@ -70,6 +70,8 @@ def soco_fixture(music_library, speaker_info, battery_info, alarm_clock):
|
|||||||
mock_soco.night_mode = True
|
mock_soco.night_mode = True
|
||||||
mock_soco.dialog_mode = True
|
mock_soco.dialog_mode = True
|
||||||
mock_soco.volume = 19
|
mock_soco.volume = 19
|
||||||
|
mock_soco.bass = 1
|
||||||
|
mock_soco.treble = -1
|
||||||
mock_soco.get_battery_info.return_value = battery_info
|
mock_soco.get_battery_info.return_value = battery_info
|
||||||
mock_soco.all_zones = [mock_soco]
|
mock_soco.all_zones = [mock_soco]
|
||||||
yield mock_soco
|
yield mock_soco
|
||||||
|
Loading…
x
Reference in New Issue
Block a user