Add mute support to Russound RIO (#134118)

pull/134130/head
Noah Husby 2024-12-28 03:22:13 -05:00 committed by GitHub
parent 6edf06f8a4
commit aceb1b39ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 78 additions and 11 deletions

View File

@ -3,9 +3,6 @@
import asyncio import asyncio
from aiorussound import CommandError from aiorussound import CommandError
from aiorussound.const import FeatureFlag
from homeassistant.components.media_player import MediaPlayerEntityFeature
DOMAIN = "russound_rio" DOMAIN = "russound_rio"
@ -15,7 +12,3 @@ RUSSOUND_RIO_EXCEPTIONS = (
TimeoutError, TimeoutError,
asyncio.CancelledError, asyncio.CancelledError,
) )
MP_FEATURES_BY_FLAG = {
FeatureFlag.COMMANDS_ZONE_MUTE_OFF_ON: MediaPlayerEntityFeature.VOLUME_MUTE
}

View File

@ -22,7 +22,6 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import RussoundConfigEntry from . import RussoundConfigEntry
from .const import MP_FEATURES_BY_FLAG
from .entity import RussoundBaseEntity, command from .entity import RussoundBaseEntity, command
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -54,6 +53,7 @@ class RussoundZoneDevice(RussoundBaseEntity, MediaPlayerEntity):
_attr_supported_features = ( _attr_supported_features = (
MediaPlayerEntityFeature.VOLUME_SET MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.VOLUME_STEP | MediaPlayerEntityFeature.VOLUME_STEP
| MediaPlayerEntityFeature.VOLUME_MUTE
| MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.TURN_OFF | MediaPlayerEntityFeature.TURN_OFF
| MediaPlayerEntityFeature.SELECT_SOURCE | MediaPlayerEntityFeature.SELECT_SOURCE
@ -69,9 +69,6 @@ class RussoundZoneDevice(RussoundBaseEntity, MediaPlayerEntity):
self._sources = sources self._sources = sources
self._attr_name = _zone.name self._attr_name = _zone.name
self._attr_unique_id = f"{self._primary_mac_address}-{_zone.device_str}" self._attr_unique_id = f"{self._primary_mac_address}-{_zone.device_str}"
for flag, feature in MP_FEATURES_BY_FLAG.items():
if flag in self._client.supported_features:
self._attr_supported_features |= feature
@property @property
def _zone(self) -> ZoneControlSurface: def _zone(self) -> ZoneControlSurface:
@ -150,6 +147,11 @@ class RussoundZoneDevice(RussoundBaseEntity, MediaPlayerEntity):
""" """
return self._zone.volume / 50.0 return self._zone.volume / 50.0
@property
def is_volume_muted(self) -> bool:
"""Return whether zone is muted."""
return self._zone.is_mute
@command @command
async def async_turn_off(self) -> None: async def async_turn_off(self) -> None:
"""Turn off the zone.""" """Turn off the zone."""
@ -184,3 +186,16 @@ class RussoundZoneDevice(RussoundBaseEntity, MediaPlayerEntity):
async def async_volume_down(self) -> None: async def async_volume_down(self) -> None:
"""Step the volume down.""" """Step the volume down."""
await self._zone.volume_down() await self._zone.volume_down()
@command
async def async_mute_volume(self, mute: bool) -> None:
"""Mute the media player."""
if FeatureFlag.COMMANDS_ZONE_MUTE_OFF_ON in self._client.supported_features:
if mute:
await self._zone.mute()
else:
await self._zone.unmute()
return
if mute != self.is_volume_muted:
await self._zone.toggle_mute()

View File

@ -75,6 +75,9 @@ def mock_russound_client() -> Generator[AsyncMock]:
zone.zone_on = AsyncMock() zone.zone_on = AsyncMock()
zone.zone_off = AsyncMock() zone.zone_off = AsyncMock()
zone.select_source = AsyncMock() zone.select_source = AsyncMock()
zone.mute = AsyncMock()
zone.unmute = AsyncMock()
zone.toggle_mute = AsyncMock()
client.controllers = { client.controllers = {
1: Controller( 1: Controller(

View File

@ -2,6 +2,7 @@
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
from aiorussound.const import FeatureFlag
from aiorussound.exceptions import CommandError from aiorussound.exceptions import CommandError
from aiorussound.models import PlayStatus from aiorussound.models import PlayStatus
import pytest import pytest
@ -9,6 +10,7 @@ import pytest
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE,
ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_VOLUME_MUTED,
DOMAIN as MP_DOMAIN, DOMAIN as MP_DOMAIN,
SERVICE_SELECT_SOURCE, SERVICE_SELECT_SOURCE,
) )
@ -17,6 +19,7 @@ from homeassistant.const import (
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
SERVICE_TURN_ON, SERVICE_TURN_ON,
SERVICE_VOLUME_DOWN, SERVICE_VOLUME_DOWN,
SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_SET, SERVICE_VOLUME_SET,
SERVICE_VOLUME_UP, SERVICE_VOLUME_UP,
STATE_BUFFERING, STATE_BUFFERING,
@ -106,6 +109,59 @@ async def test_media_volume(
) )
async def test_volume_mute(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_russound_client: AsyncMock,
) -> None:
"""Test mute service."""
await setup_integration(hass, mock_config_entry)
# Test mute (w/ toggle mute support)
await hass.services.async_call(
MP_DOMAIN,
SERVICE_VOLUME_MUTE,
{ATTR_ENTITY_ID: ENTITY_ID_ZONE_1, ATTR_MEDIA_VOLUME_MUTED: True},
blocking=True,
)
mock_russound_client.controllers[1].zones[1].toggle_mute.assert_called_once()
mock_russound_client.controllers[1].zones[1].toggle_mute.reset_mock()
mock_russound_client.controllers[1].zones[1].is_mute = True
# Test mute when already muted (w/ toggle mute support)
await hass.services.async_call(
MP_DOMAIN,
SERVICE_VOLUME_MUTE,
{ATTR_ENTITY_ID: ENTITY_ID_ZONE_1, ATTR_MEDIA_VOLUME_MUTED: True},
blocking=True,
)
mock_russound_client.controllers[1].zones[1].toggle_mute.assert_not_called()
mock_russound_client.supported_features = [FeatureFlag.COMMANDS_ZONE_MUTE_OFF_ON]
# Test mute (w/ dedicated commands)
await hass.services.async_call(
MP_DOMAIN,
SERVICE_VOLUME_MUTE,
{ATTR_ENTITY_ID: ENTITY_ID_ZONE_1, ATTR_MEDIA_VOLUME_MUTED: True},
blocking=True,
)
mock_russound_client.controllers[1].zones[1].mute.assert_called_once()
# Test unmute (w/ dedicated commands)
await hass.services.async_call(
MP_DOMAIN,
SERVICE_VOLUME_MUTE,
{ATTR_ENTITY_ID: ENTITY_ID_ZONE_1, ATTR_MEDIA_VOLUME_MUTED: False},
blocking=True,
)
mock_russound_client.controllers[1].zones[1].unmute.assert_called_once()
@pytest.mark.parametrize( @pytest.mark.parametrize(
("source_name", "source_id"), ("source_name", "source_id"),
[ [