Skip to content

Commit

Permalink
Add mute support to Russound RIO (#134118)
Browse files Browse the repository at this point in the history
  • Loading branch information
noahhusby authored Dec 28, 2024
1 parent 6edf06f commit aceb1b3
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 11 deletions.
7 changes: 0 additions & 7 deletions homeassistant/components/russound_rio/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
import asyncio

from aiorussound import CommandError
from aiorussound.const import FeatureFlag

from homeassistant.components.media_player import MediaPlayerEntityFeature

DOMAIN = "russound_rio"

Expand All @@ -15,7 +12,3 @@
TimeoutError,
asyncio.CancelledError,
)

MP_FEATURES_BY_FLAG = {
FeatureFlag.COMMANDS_ZONE_MUTE_OFF_ON: MediaPlayerEntityFeature.VOLUME_MUTE
}
23 changes: 19 additions & 4 deletions homeassistant/components/russound_rio/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import RussoundConfigEntry
from .const import MP_FEATURES_BY_FLAG
from .entity import RussoundBaseEntity, command

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -54,6 +53,7 @@ class RussoundZoneDevice(RussoundBaseEntity, MediaPlayerEntity):
_attr_supported_features = (
MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.VOLUME_STEP
| MediaPlayerEntityFeature.VOLUME_MUTE
| MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.TURN_OFF
| MediaPlayerEntityFeature.SELECT_SOURCE
Expand All @@ -69,9 +69,6 @@ def __init__(
self._sources = sources
self._attr_name = _zone.name
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
def _zone(self) -> ZoneControlSurface:
Expand Down Expand Up @@ -150,6 +147,11 @@ def volume_level(self) -> float:
"""
return self._zone.volume / 50.0

@property
def is_volume_muted(self) -> bool:
"""Return whether zone is muted."""
return self._zone.is_mute

@command
async def async_turn_off(self) -> None:
"""Turn off the zone."""
Expand Down Expand Up @@ -184,3 +186,16 @@ async def async_volume_up(self) -> None:
async def async_volume_down(self) -> None:
"""Step the 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()
3 changes: 3 additions & 0 deletions tests/components/russound_rio/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ def mock_russound_client() -> Generator[AsyncMock]:
zone.zone_on = AsyncMock()
zone.zone_off = AsyncMock()
zone.select_source = AsyncMock()
zone.mute = AsyncMock()
zone.unmute = AsyncMock()
zone.toggle_mute = AsyncMock()

client.controllers = {
1: Controller(
Expand Down
56 changes: 56 additions & 0 deletions tests/components/russound_rio/test_media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

from unittest.mock import AsyncMock

from aiorussound.const import FeatureFlag
from aiorussound.exceptions import CommandError
from aiorussound.models import PlayStatus
import pytest

from homeassistant.components.media_player import (
ATTR_INPUT_SOURCE,
ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_VOLUME_MUTED,
DOMAIN as MP_DOMAIN,
SERVICE_SELECT_SOURCE,
)
Expand All @@ -17,6 +19,7 @@
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SERVICE_VOLUME_DOWN,
SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_SET,
SERVICE_VOLUME_UP,
STATE_BUFFERING,
Expand Down Expand Up @@ -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(
("source_name", "source_id"),
[
Expand Down

0 comments on commit aceb1b3

Please sign in to comment.