Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mute support to Russound RIO #134118

Merged
merged 3 commits into from
Dec 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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