1 """Entity representing a Sonos number control."""
3 from __future__
import annotations
6 from typing
import cast
15 from .const
import SONOS_CREATE_LEVELS
16 from .entity
import SonosEntity
17 from .helpers
import soco_error
18 from .speaker
import SonosSpeaker
21 "audio_delay": (0, 5),
23 "balance": (-100, 100),
25 "sub_crossover": (50, 110),
26 "sub_gain": (-15, 15),
27 "surround_level": (-15, 15),
28 "music_surround_level": (-15, 15),
31 type SocoFeatures = list[tuple[str, tuple[int, int]]]
33 _LOGGER = logging.getLogger(__name__)
37 """Represent a balance measure returned by SoCo as a number.
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.
47 return (right - left) * 100 //
max(right, left)
51 """Convert a balance value from -100 to 100 into SoCo format.
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),
61 left =
min(100, 100 -
int(value))
62 right =
min(100,
int(value) + 100)
66 LEVEL_TO_NUMBER = {
"balance": _balance_to_number}
67 LEVEL_FROM_NUMBER = {
"balance": _balance_from_number}
72 config_entry: ConfigEntry,
73 async_add_entities: AddEntitiesCallback,
75 """Set up the Sonos number platform from a config entry."""
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))
88 available_features = await hass.async_add_executor_job(
89 available_soco_attributes, speaker
92 for level_type, valid_range
in available_features:
94 "Creating %s number control on %s", level_type, speaker.zone_name
99 config_entry.async_on_unload(
105 """Representation of a Sonos level entity."""
107 _attr_entity_category = EntityCategory.CONFIG
110 self, speaker: SonosSpeaker, level_type: str, valid_range: tuple[int, int]
112 """Initialize the level entity."""
120 """Poll the value if subscriptions are not working."""
121 await self.
hasshass.async_add_executor_job(self.
poll_statepoll_state)
125 """Poll the device for the current state."""
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))
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)
None set_native_value(self, float value)
None _async_fallback_poll(self)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
tuple[int, int] _balance_from_number(float value)
float _balance_to_number(tuple[int, int] state)
list[TemplateNumber] _async_create_entities(HomeAssistant hass, list[dict[str, Any]] definitions, str|None unique_id_prefix)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)