Add mute support to Russound RIO (#134118)
parent
6edf06f8a4
commit
aceb1b39ba
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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"),
|
||||||
[
|
[
|
||||||
|
|
Loading…
Reference in New Issue