Home Assistant Unofficial Reference 2024.12.1
number.py
Go to the documentation of this file.
1 """Entity representing a Sonos number control."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import cast
7 
8 from homeassistant.components.number import NumberEntity
9 from homeassistant.config_entries import ConfigEntry
10 from homeassistant.const import EntityCategory
11 from homeassistant.core import HomeAssistant
12 from homeassistant.helpers.dispatcher import async_dispatcher_connect
13 from homeassistant.helpers.entity_platform import AddEntitiesCallback
14 
15 from .const import SONOS_CREATE_LEVELS
16 from .entity import SonosEntity
17 from .helpers import soco_error
18 from .speaker import SonosSpeaker
19 
20 LEVEL_TYPES = {
21  "audio_delay": (0, 5),
22  "bass": (-10, 10),
23  "balance": (-100, 100),
24  "treble": (-10, 10),
25  "sub_crossover": (50, 110),
26  "sub_gain": (-15, 15),
27  "surround_level": (-15, 15),
28  "music_surround_level": (-15, 15),
29 }
30 
31 type SocoFeatures = list[tuple[str, tuple[int, int]]]
32 
33 _LOGGER = logging.getLogger(__name__)
34 
35 
36 def _balance_to_number(state: tuple[int, int]) -> float:
37  """Represent a balance measure returned by SoCo as a number.
38 
39  SoCo returns a pair of volumes, one for the left side and one
40  for the right side. When the two are equal, sound is centered;
41  HA will show that as 0. When the left side is louder, HA will
42  show a negative value, and a positive value means the right
43  side is louder. Maximum absolute value is 100, which means only
44  one side produces sound at all.
45  """
46  left, right = state
47  return (right - left) * 100 // max(right, left)
48 
49 
50 def _balance_from_number(value: float) -> tuple[int, int]:
51  """Convert a balance value from -100 to 100 into SoCo format.
52 
53  0 becomes (100, 100), fully enabling both sides. Note that
54  the master volume control is separate, so this does not
55  turn up the speakers to maximum volume. Negative values
56  reduce the volume of the right side, and positive values
57  reduce the volume of the left side. -100 becomes (100, 0),
58  fully muting the right side, and +100 becomes (0, 100),
59  muting the left side.
60  """
61  left = min(100, 100 - int(value))
62  right = min(100, int(value) + 100)
63  return left, right
64 
65 
66 LEVEL_TO_NUMBER = {"balance": _balance_to_number}
67 LEVEL_FROM_NUMBER = {"balance": _balance_from_number}
68 
69 
71  hass: HomeAssistant,
72  config_entry: ConfigEntry,
73  async_add_entities: AddEntitiesCallback,
74 ) -> None:
75  """Set up the Sonos number platform from a config entry."""
76 
77  def available_soco_attributes(speaker: SonosSpeaker) -> SocoFeatures:
78  features: SocoFeatures = []
79  for level_type, valid_range in LEVEL_TYPES.items():
80  if (state := getattr(speaker.soco, level_type, None)) is not None:
81  setattr(speaker, level_type, state)
82  features.append((level_type, valid_range))
83  return features
84 
85  async def _async_create_entities(speaker: SonosSpeaker) -> None:
86  entities = []
87 
88  available_features = await hass.async_add_executor_job(
89  available_soco_attributes, speaker
90  )
91 
92  for level_type, valid_range in available_features:
93  _LOGGER.debug(
94  "Creating %s number control on %s", level_type, speaker.zone_name
95  )
96  entities.append(SonosLevelEntity(speaker, level_type, valid_range))
97  async_add_entities(entities)
98 
99  config_entry.async_on_unload(
100  async_dispatcher_connect(hass, SONOS_CREATE_LEVELS, _async_create_entities)
101  )
102 
103 
105  """Representation of a Sonos level entity."""
106 
107  _attr_entity_category = EntityCategory.CONFIG
108 
109  def __init__(
110  self, speaker: SonosSpeaker, level_type: str, valid_range: tuple[int, int]
111  ) -> None:
112  """Initialize the level entity."""
113  super().__init__(speaker)
114  self._attr_unique_id_attr_unique_id = f"{self.soco.uid}-{level_type}"
115  self._attr_translation_key_attr_translation_key = level_type
116  self.level_typelevel_type = level_type
117  self._attr_native_min_value, self._attr_native_max_value_attr_native_max_value = valid_range
118 
119  async def _async_fallback_poll(self) -> None:
120  """Poll the value if subscriptions are not working."""
121  await self.hasshass.async_add_executor_job(self.poll_statepoll_state)
122 
123  @soco_error()
124  def poll_state(self) -> None:
125  """Poll the device for the current state."""
126  state = getattr(self.socosoco, self.level_typelevel_type)
127  setattr(self.speakerspeaker, self.level_typelevel_type, state)
128 
129  @soco_error()
130  def set_native_value(self, value: float) -> None:
131  """Set a new value."""
132  from_number = LEVEL_FROM_NUMBER.get(self.level_typelevel_type, int)
133  setattr(self.socosoco, self.level_typelevel_type, from_number(value))
134 
135  @property
136  def native_value(self) -> float:
137  """Return the current value."""
138  to_number = LEVEL_TO_NUMBER.get(self.level_typelevel_type, int)
139  return cast(float, to_number(getattr(self.speakerspeaker, self.level_typelevel_type)))
None __init__(self, SonosSpeaker speaker, str level_type, tuple[int, int] valid_range)
Definition: number.py:111
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: number.py:74
tuple[int, int] _balance_from_number(float value)
Definition: number.py:50
float _balance_to_number(tuple[int, int] state)
Definition: number.py:36
list[TemplateNumber] _async_create_entities(HomeAssistant hass, list[dict[str, Any]] definitions, str|None unique_id_prefix)
Definition: number.py:83
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103