Skip to content

Commit b4747ea

Browse files
PeteRagerjoostlek
authored andcommitted
Fix Sonos Dialog Select type conversion part II (home-assistant#152491)
Co-authored-by: Joost Lekkerkerker <[email protected]>
1 parent df69bce commit b4747ea

File tree

3 files changed

+66
-17
lines changed

3 files changed

+66
-17
lines changed

homeassistant/components/sonos/select.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,12 @@ def available_soco_attributes(
5959
for select_data in SELECT_TYPES:
6060
if select_data.speaker_model == speaker.model_name.upper():
6161
if (
62-
state := getattr(speaker.soco, select_data.soco_attribute, None)
63-
) is not None:
64-
try:
65-
setattr(speaker, select_data.speaker_attribute, int(state))
66-
features.append(select_data)
67-
except ValueError:
68-
_LOGGER.error(
69-
"Invalid value for %s %s",
70-
select_data.speaker_attribute,
71-
state,
72-
)
62+
speaker.update_soco_int_attribute(
63+
select_data.soco_attribute, select_data.speaker_attribute
64+
)
65+
is not None
66+
):
67+
features.append(select_data)
7368
return features
7469

7570
async def _async_create_entities(speaker: SonosSpeaker) -> None:
@@ -112,8 +107,9 @@ async def _async_fallback_poll(self) -> None:
112107
@soco_error()
113108
def poll_state(self) -> None:
114109
"""Poll the device for the current state."""
115-
state = getattr(self.soco, self.soco_attribute)
116-
setattr(self.speaker, self.speaker_attribute, state)
110+
self.speaker.update_soco_int_attribute(
111+
self.soco_attribute, self.speaker_attribute
112+
)
117113

118114
@property
119115
def current_option(self) -> str | None:

homeassistant/components/sonos/speaker.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,29 @@ def async_write_entity_states(self) -> None:
275275
"""Write states for associated SonosEntity instances."""
276276
async_dispatcher_send(self.hass, f"{SONOS_STATE_UPDATED}-{self.soco.uid}")
277277

278+
def update_soco_int_attribute(
279+
self, soco_attribute: str, speaker_attribute: str
280+
) -> int | None:
281+
"""Update an integer attribute from SoCo and set it on the speaker.
282+
283+
Returns the integer value if successful, otherwise None. Do not call from
284+
async context as it is a blocking function.
285+
"""
286+
value: int | None = None
287+
if (state := getattr(self.soco, soco_attribute, None)) is None:
288+
_LOGGER.error("Missing value for %s", speaker_attribute)
289+
else:
290+
try:
291+
value = int(state)
292+
except (TypeError, ValueError):
293+
_LOGGER.error(
294+
"Invalid value for %s %s",
295+
speaker_attribute,
296+
state,
297+
)
298+
setattr(self, speaker_attribute, value)
299+
return value
300+
278301
#
279302
# Properties
280303
#

tests/components/sonos/test_select.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,36 @@ async def test_select_dialog_invalid_level(
8888
assert dialog_level_state.state == STATE_UNKNOWN
8989

9090

91+
@pytest.mark.parametrize(
92+
("value", "result"),
93+
[
94+
("invalid_integer", "Invalid value for dialog_level_enum invalid_integer"),
95+
(None, "Missing value for dialog_level_enum"),
96+
],
97+
)
98+
async def test_select_dialog_value_error(
99+
hass: HomeAssistant,
100+
async_setup_sonos,
101+
soco,
102+
entity_registry: er.EntityRegistry,
103+
speaker_info: dict[str, str],
104+
caplog: pytest.LogCaptureFixture,
105+
value: str | None,
106+
result: str,
107+
) -> None:
108+
"""Test receiving a value from Sonos that is not convertible to an integer."""
109+
110+
speaker_info["model_name"] = MODEL_SONOS_ARC_ULTRA.lower()
111+
soco.get_speaker_info.return_value = speaker_info
112+
soco.dialog_level = value
113+
114+
with caplog.at_level(logging.WARNING):
115+
await async_setup_sonos()
116+
assert result in caplog.text
117+
118+
assert SELECT_DIALOG_LEVEL_ENTITY not in entity_registry.entities
119+
120+
91121
@pytest.mark.parametrize(
92122
("result", "option"),
93123
[
@@ -149,12 +179,12 @@ async def test_select_dialog_level_event(
149179

150180
speaker_info["model_name"] = MODEL_SONOS_ARC_ULTRA.lower()
151181
soco.get_speaker_info.return_value = speaker_info
152-
soco.dialog_level = 0
182+
soco.dialog_level = "0"
153183

154184
await async_setup_sonos()
155185

156186
event = create_rendering_control_event(soco)
157-
event.variables[ATTR_DIALOG_LEVEL] = 3
187+
event.variables[ATTR_DIALOG_LEVEL] = "3"
158188
soco.renderingControl.subscribe.return_value._callback(event)
159189
await hass.async_block_till_done(wait_background_tasks=True)
160190

@@ -175,11 +205,11 @@ async def test_select_dialog_level_poll(
175205

176206
speaker_info["model_name"] = MODEL_SONOS_ARC_ULTRA.lower()
177207
soco.get_speaker_info.return_value = speaker_info
178-
soco.dialog_level = 0
208+
soco.dialog_level = "0"
179209

180210
await async_setup_sonos()
181211

182-
soco.dialog_level = 4
212+
soco.dialog_level = "4"
183213

184214
freezer.tick(SCAN_INTERVAL)
185215
async_fire_time_changed(hass)

0 commit comments

Comments
 (0)