Add Sound Mode selection in soundpal components (#106589)
This commit is contained in:
parent
135fe26704
commit
586d27320e
3 changed files with 109 additions and 1 deletions
|
@ -11,9 +11,11 @@ from songpal import (
|
|||
ContentChange,
|
||||
Device,
|
||||
PowerChange,
|
||||
SettingChange,
|
||||
SongpalException,
|
||||
VolumeChange,
|
||||
)
|
||||
from songpal.containers import Setting
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
|
@ -99,6 +101,7 @@ class SongpalEntity(MediaPlayerEntity):
|
|||
| MediaPlayerEntityFeature.VOLUME_STEP
|
||||
| MediaPlayerEntityFeature.VOLUME_MUTE
|
||||
| MediaPlayerEntityFeature.SELECT_SOURCE
|
||||
| MediaPlayerEntityFeature.SELECT_SOUND_MODE
|
||||
| MediaPlayerEntityFeature.TURN_ON
|
||||
| MediaPlayerEntityFeature.TURN_OFF
|
||||
)
|
||||
|
@ -124,6 +127,8 @@ class SongpalEntity(MediaPlayerEntity):
|
|||
|
||||
self._active_source = None
|
||||
self._sources = {}
|
||||
self._active_sound_mode = None
|
||||
self._sound_modes = {}
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Run when entity is added to hass."""
|
||||
|
@ -133,6 +138,28 @@ class SongpalEntity(MediaPlayerEntity):
|
|||
"""Run when entity will be removed from hass."""
|
||||
await self._dev.stop_listen_notifications()
|
||||
|
||||
async def _get_sound_modes_info(self):
|
||||
"""Get available sound modes and the active one."""
|
||||
settings = await self._dev.get_sound_settings("soundField")
|
||||
if isinstance(settings, Setting):
|
||||
settings = [settings]
|
||||
|
||||
sound_modes = {}
|
||||
active_sound_mode = None
|
||||
for setting in settings:
|
||||
cur = setting.currentValue
|
||||
for opt in setting.candidate:
|
||||
if not opt.isAvailable:
|
||||
continue
|
||||
if opt.value == cur:
|
||||
active_sound_mode = opt.value
|
||||
sound_modes[opt.value] = opt
|
||||
|
||||
_LOGGER.debug("Got sound modes: %s", sound_modes)
|
||||
_LOGGER.debug("Active sound mode: %s", active_sound_mode)
|
||||
|
||||
return active_sound_mode, sound_modes
|
||||
|
||||
async def async_activate_websocket(self):
|
||||
"""Activate websocket for listening if wanted."""
|
||||
_LOGGER.info("Activating websocket connection")
|
||||
|
@ -152,6 +179,16 @@ class SongpalEntity(MediaPlayerEntity):
|
|||
else:
|
||||
_LOGGER.debug("Got non-handled content change: %s", content)
|
||||
|
||||
async def _setting_changed(setting: SettingChange):
|
||||
_LOGGER.debug("Setting changed: %s", setting)
|
||||
|
||||
if setting.target == "soundField":
|
||||
self._active_sound_mode = setting.currentValue
|
||||
_LOGGER.debug("New active sound mode: %s", self._active_sound_mode)
|
||||
self.async_write_ha_state()
|
||||
else:
|
||||
_LOGGER.debug("Got non-handled setting change: %s", setting)
|
||||
|
||||
async def _power_changed(power: PowerChange):
|
||||
_LOGGER.debug("Power changed: %s", power)
|
||||
self._state = power.status
|
||||
|
@ -192,6 +229,7 @@ class SongpalEntity(MediaPlayerEntity):
|
|||
self._dev.on_notification(VolumeChange, _volume_changed)
|
||||
self._dev.on_notification(ContentChange, _source_changed)
|
||||
self._dev.on_notification(PowerChange, _power_changed)
|
||||
self._dev.on_notification(SettingChange, _setting_changed)
|
||||
self._dev.on_notification(ConnectChange, _try_reconnect)
|
||||
|
||||
async def handle_stop(event):
|
||||
|
@ -271,6 +309,11 @@ class SongpalEntity(MediaPlayerEntity):
|
|||
|
||||
_LOGGER.debug("Active source: %s", self._active_source)
|
||||
|
||||
(
|
||||
self._active_sound_mode,
|
||||
self._sound_modes,
|
||||
) = await self._get_sound_modes_info()
|
||||
|
||||
self._attr_available = True
|
||||
|
||||
except SongpalException as ex:
|
||||
|
@ -291,6 +334,27 @@ class SongpalEntity(MediaPlayerEntity):
|
|||
"""Return list of available sources."""
|
||||
return [src.title for src in self._sources.values()]
|
||||
|
||||
async def async_select_sound_mode(self, sound_mode: str) -> None:
|
||||
"""Select sound mode."""
|
||||
for mode in self._sound_modes.values():
|
||||
if mode.title == sound_mode:
|
||||
await self._dev.set_sound_settings("soundField", mode.value)
|
||||
return
|
||||
|
||||
_LOGGER.error("Unable to find sound mode: %s", sound_mode)
|
||||
|
||||
@property
|
||||
def sound_mode_list(self) -> list[str] | None:
|
||||
"""Return list of available sound modes.
|
||||
|
||||
When active mode is None it means that sound mode is unavailable on the sound bar.
|
||||
Can be due to incompatible sound bar or the sound bar is in a mode that does not
|
||||
support sound mode changes.
|
||||
"""
|
||||
if not self._active_sound_mode:
|
||||
return None
|
||||
return [sound_mode.title for sound_mode in self._sound_modes.values()]
|
||||
|
||||
@property
|
||||
def state(self) -> MediaPlayerState:
|
||||
"""Return current state."""
|
||||
|
@ -304,6 +368,12 @@ class SongpalEntity(MediaPlayerEntity):
|
|||
# Avoid a KeyError when _active_source is not (yet) populated
|
||||
return getattr(self._active_source, "title", None)
|
||||
|
||||
@property
|
||||
def sound_mode(self) -> str | None:
|
||||
"""Return currently active sound_mode."""
|
||||
active_sound_mode = self._sound_modes.get(self._active_sound_mode)
|
||||
return active_sound_mode.title if active_sound_mode else None
|
||||
|
||||
@property
|
||||
def volume_level(self):
|
||||
"""Return volume level."""
|
||||
|
|
|
@ -85,6 +85,24 @@ def _create_mocked_device(throw_exception=False, wired_mac=MAC, wireless_mac=Non
|
|||
input2.active = True
|
||||
type(mocked_device).get_inputs = AsyncMock(return_value=[input1, input2])
|
||||
|
||||
sound_mode1 = MagicMock()
|
||||
sound_mode1.title = "Sound Mode 1"
|
||||
sound_mode1.value = "sound_mode1"
|
||||
sound_mode1.isAvailable = True
|
||||
sound_mode2 = MagicMock()
|
||||
sound_mode2.title = "Sound Mode 2"
|
||||
sound_mode2.value = "sound_mode2"
|
||||
sound_mode2.isAvailable = True
|
||||
sound_mode3 = MagicMock()
|
||||
sound_mode3.title = "Sound Mode 3"
|
||||
sound_mode3.value = "sound_mode3"
|
||||
sound_mode3.isAvailable = False
|
||||
|
||||
soundField = MagicMock()
|
||||
soundField.currentValue = "sound_mode2"
|
||||
soundField.candidate = [sound_mode1, sound_mode2, sound_mode3]
|
||||
type(mocked_device).get_sound_settings = AsyncMock(return_value=[soundField])
|
||||
|
||||
type(mocked_device).set_power = AsyncMock()
|
||||
type(mocked_device).set_sound_settings = AsyncMock()
|
||||
type(mocked_device).listen_notifications = AsyncMock()
|
||||
|
|
|
@ -12,6 +12,7 @@ from songpal import (
|
|||
SongpalException,
|
||||
VolumeChange,
|
||||
)
|
||||
from songpal.notification import SettingChange
|
||||
|
||||
from homeassistant.components import media_player, songpal
|
||||
from homeassistant.components.media_player import MediaPlayerEntityFeature
|
||||
|
@ -47,6 +48,7 @@ SUPPORT_SONGPAL = (
|
|||
| MediaPlayerEntityFeature.VOLUME_STEP
|
||||
| MediaPlayerEntityFeature.VOLUME_MUTE
|
||||
| MediaPlayerEntityFeature.SELECT_SOURCE
|
||||
| MediaPlayerEntityFeature.SELECT_SOUND_MODE
|
||||
| MediaPlayerEntityFeature.TURN_ON
|
||||
| MediaPlayerEntityFeature.TURN_OFF
|
||||
)
|
||||
|
@ -138,6 +140,8 @@ async def test_state(hass: HomeAssistant) -> None:
|
|||
assert attributes["is_volume_muted"] is False
|
||||
assert attributes["source_list"] == ["title1", "title2"]
|
||||
assert attributes["source"] == "title2"
|
||||
assert attributes["sound_mode_list"] == ["Sound Mode 1", "Sound Mode 2"]
|
||||
assert attributes["sound_mode"] == "Sound Mode 2"
|
||||
assert attributes["supported_features"] == SUPPORT_SONGPAL
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
|
@ -171,6 +175,8 @@ async def test_state_wireless(hass: HomeAssistant) -> None:
|
|||
assert attributes["is_volume_muted"] is False
|
||||
assert attributes["source_list"] == ["title1", "title2"]
|
||||
assert attributes["source"] == "title2"
|
||||
assert attributes["sound_mode_list"] == ["Sound Mode 1", "Sound Mode 2"]
|
||||
assert attributes["sound_mode"] == "Sound Mode 2"
|
||||
assert attributes["supported_features"] == SUPPORT_SONGPAL
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
|
@ -206,6 +212,8 @@ async def test_state_both(hass: HomeAssistant) -> None:
|
|||
assert attributes["is_volume_muted"] is False
|
||||
assert attributes["source_list"] == ["title1", "title2"]
|
||||
assert attributes["source"] == "title2"
|
||||
assert attributes["sound_mode_list"] == ["Sound Mode 1", "Sound Mode 2"]
|
||||
assert attributes["sound_mode"] == "Sound Mode 2"
|
||||
assert attributes["supported_features"] == SUPPORT_SONGPAL
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
|
@ -303,6 +311,9 @@ async def test_services(hass: HomeAssistant) -> None:
|
|||
mocked_device2.set_sound_settings.assert_called_once_with("name", "value")
|
||||
mocked_device3.set_sound_settings.assert_called_once_with("name", "value")
|
||||
|
||||
await _call(hass, media_player.SERVICE_SELECT_SOUND_MODE, sound_mode="Sound Mode 1")
|
||||
mocked_device.set_sound_settings.assert_called_with("soundField", "sound_mode1")
|
||||
|
||||
|
||||
async def test_websocket_events(hass: HomeAssistant) -> None:
|
||||
"""Test websocket events."""
|
||||
|
@ -315,7 +326,7 @@ async def test_websocket_events(hass: HomeAssistant) -> None:
|
|||
await hass.async_block_till_done()
|
||||
|
||||
mocked_device.listen_notifications.assert_called_once()
|
||||
assert mocked_device.on_notification.call_count == 4
|
||||
assert mocked_device.on_notification.call_count == 5
|
||||
|
||||
notification_callbacks = mocked_device.notification_callbacks
|
||||
|
||||
|
@ -336,6 +347,15 @@ async def test_websocket_events(hass: HomeAssistant) -> None:
|
|||
await notification_callbacks[ContentChange](content_change)
|
||||
assert _get_attributes(hass)["source"] == "title1"
|
||||
|
||||
sound_mode_change = MagicMock()
|
||||
sound_mode_change.target = "soundField"
|
||||
sound_mode_change.currentValue = "sound_mode1"
|
||||
await notification_callbacks[SettingChange](sound_mode_change)
|
||||
assert _get_attributes(hass)["sound_mode"] == "Sound Mode 1"
|
||||
sound_mode_change.currentValue = "sound_mode2"
|
||||
await notification_callbacks[SettingChange](sound_mode_change)
|
||||
assert _get_attributes(hass)["sound_mode"] == "Sound Mode 2"
|
||||
|
||||
power_change = MagicMock()
|
||||
power_change.status = False
|
||||
await notification_callbacks[PowerChange](power_change)
|
||||
|
|
Loading…
Add table
Reference in a new issue