Move Sonos bass & treble controls to number entities (#60498)

This commit is contained in:
jjlawren 2021-11-29 09:00:37 -06:00 committed by GitHub
parent 3aa35e15c2
commit a88cc8b98c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 68 deletions

View File

@ -19,6 +19,7 @@ from homeassistant.components.media_player.const import (
MEDIA_TYPE_PLAYLIST,
MEDIA_TYPE_TRACK,
)
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_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"
DATA_SONOS = "sonos_media_player"
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_ALBUM = "albums"
@ -139,6 +146,7 @@ SONOS_CHECK_ACTIVITY = "sonos_check_activity"
SONOS_CREATE_ALARM = "sonos_create_alarm"
SONOS_CREATE_BATTERY = "sonos_create_battery"
SONOS_CREATE_SWITCHES = "sonos_create_switches"
SONOS_CREATE_LEVELS = "sonos_create_levels"
SONOS_CREATE_MEDIA_PLAYER = "sonos_create_media_player"
SONOS_ENTITY_CREATED = "sonos_entity_created"
SONOS_POLL_UPDATE = "sonos_poll_update"

View File

@ -108,7 +108,6 @@ SERVICE_RESTORE = "restore"
SERVICE_SET_TIMER = "set_sleep_timer"
SERVICE_CLEAR_TIMER = "clear_sleep_timer"
SERVICE_UPDATE_ALARM = "update_alarm"
SERVICE_SET_OPTION = "set_option"
SERVICE_PLAY_QUEUE = "play_queue"
SERVICE_REMOVE_FROM_QUEUE = "remove_from_queue"
@ -120,8 +119,6 @@ ATTR_INCLUDE_LINKED_ZONES = "include_linked_zones"
ATTR_MASTER = "master"
ATTR_WITH_GROUP = "with_group"
ATTR_QUEUE_POSITION = "queue_position"
ATTR_EQ_BASS = "bass_level"
ATTR_EQ_TREBLE = "treble_level"
async def async_setup_entry(
@ -225,19 +222,6 @@ async def async_setup_entry(
"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
SERVICE_PLAY_QUEUE,
{vol.Optional(ATTR_QUEUE_POSITION): cv.positive_int},
@ -605,19 +589,6 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
alarm.include_linked_zones = include_linked_zones
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()
def play_queue(self, queue_position: int = 0) -> None:
"""Start playing the queue."""
@ -635,12 +606,6 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
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:
attributes[ATTR_QUEUE_POSITION] = self.media.queue_position

View 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)

View File

@ -87,30 +87,6 @@ clear_sleep_timer:
device:
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:
name: Play queue
description: Start playing the queue from the first item.

View File

@ -46,6 +46,7 @@ from .const import (
SONOS_CHECK_ACTIVITY,
SONOS_CREATE_ALARM,
SONOS_CREATE_BATTERY,
SONOS_CREATE_LEVELS,
SONOS_CREATE_MEDIA_PLAYER,
SONOS_CREATE_SWITCHES,
SONOS_ENTITY_CREATED,
@ -192,8 +193,8 @@ class SonosSpeaker:
self.night_mode: bool | None = None
self.dialog_mode: bool | None = None
self.cross_fade: bool | None = None
self.bass_level: int | None = None
self.treble_level: int | None = None
self.bass: int | None = None
self.treble: int | None = None
# Misc features
self.buttons_enabled: bool | None = None
@ -234,6 +235,8 @@ class SonosSpeaker:
)
future.result(timeout=10)
dispatcher_send(self.hass, SONOS_CREATE_LEVELS, self)
if battery_info := fetch_battery_info_or_none(self.soco):
self.battery_info = battery_info
# Battery events can be infrequent, polling is still necessary
@ -490,11 +493,11 @@ class SonosSpeaker:
if "dialog_level" in variables:
self.dialog_mode = variables["dialog_level"] == "1"
if "bass_level" in variables:
self.bass_level = variables["bass_level"]
if "bass" in variables:
self.bass = variables["bass"]
if "treble_level" in variables:
self.treble_level = variables["treble_level"]
if "treble" in variables:
self.treble = variables["treble"]
self.async_write_entity_states()
@ -968,8 +971,8 @@ class SonosSpeaker:
self.muted = self.soco.mute
self.night_mode = self.soco.night_mode
self.dialog_mode = self.soco.dialog_mode
self.bass_level = self.soco.bass
self.treble_level = self.soco.treble
self.bass = self.soco.bass
self.treble = self.soco.treble
try:
self.cross_fade = self.soco.cross_fade

View File

@ -70,6 +70,8 @@ def soco_fixture(music_library, speaker_info, battery_info, alarm_clock):
mock_soco.night_mode = True
mock_soco.dialog_mode = True
mock_soco.volume = 19
mock_soco.bass = 1
mock_soco.treble = -1
mock_soco.get_battery_info.return_value = battery_info
mock_soco.all_zones = [mock_soco]
yield mock_soco