Add entity available attribute to Cambridge Audio (#125831)
* Bump aiostreammagic to 2.2.4 * Move callback handling to entity class * Wrap all module exceptions in HA errors for Cambridge Audiopull/125837/head
parent
4afc472068
commit
a4c88a8591
|
@ -1,13 +1,38 @@
|
|||
"""Base class for Cambridge Audio entities."""
|
||||
|
||||
from collections.abc import Awaitable, Callable, Coroutine
|
||||
from functools import wraps
|
||||
from typing import Any, Concatenate
|
||||
|
||||
from aiostreammagic import StreamMagicClient
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from . import STREAM_MAGIC_EXCEPTIONS
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
def command[_EntityT: CambridgeAudioEntity, **_P](
|
||||
func: Callable[Concatenate[_EntityT, _P], Awaitable[None]],
|
||||
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
|
||||
"""Wrap async calls to raise on request error."""
|
||||
|
||||
@wraps(func)
|
||||
async def decorator(self: _EntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:
|
||||
"""Wrap all command methods."""
|
||||
try:
|
||||
await func(self, *args, **kwargs)
|
||||
except STREAM_MAGIC_EXCEPTIONS as exc:
|
||||
raise HomeAssistantError(
|
||||
f"Error executing {func.__name__} on entity {self.entity_id},"
|
||||
) from exc
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class CambridgeAudioEntity(Entity):
|
||||
"""Defines a base Cambridge Audio entity."""
|
||||
|
||||
|
@ -24,3 +49,17 @@ class CambridgeAudioEntity(Entity):
|
|||
serial_number=client.info.unit_id,
|
||||
configuration_url=f"http://{client.host}",
|
||||
)
|
||||
|
||||
@callback
|
||||
async def _state_update_callback(self, _client: StreamMagicClient) -> None:
|
||||
"""Call when the device is notified of changes."""
|
||||
self._attr_available = _client.is_connected()
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callback handlers."""
|
||||
await self.client.register_state_update_callbacks(self._state_update_callback)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Remove callbacks."""
|
||||
await self.client.unregister_state_update_callbacks(self._state_update_callback)
|
||||
|
|
|
@ -23,7 +23,7 @@ from homeassistant.config_entries import ConfigEntry
|
|||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .entity import CambridgeAudioEntity
|
||||
from .entity import CambridgeAudioEntity, command
|
||||
|
||||
BASE_FEATURES = (
|
||||
MediaPlayerEntityFeature.SELECT_SOURCE
|
||||
|
@ -70,18 +70,6 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
|
|||
super().__init__(client)
|
||||
self._attr_unique_id = client.info.unit_id
|
||||
|
||||
async def _state_update_callback(self, _client: StreamMagicClient) -> None:
|
||||
"""Call when the device is notified of changes."""
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callback handlers."""
|
||||
await self.client.register_state_update_callbacks(self._state_update_callback)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Remove callbacks."""
|
||||
await self.client.unregister_state_update_callbacks(self._state_update_callback)
|
||||
|
||||
@property
|
||||
def supported_features(self) -> MediaPlayerEntityFeature:
|
||||
"""Supported features for the media player."""
|
||||
|
@ -194,10 +182,12 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
|
|||
mode_repeat = RepeatMode.ALL
|
||||
return mode_repeat
|
||||
|
||||
@command
|
||||
async def async_media_play_pause(self) -> None:
|
||||
"""Toggle play/pause the current media."""
|
||||
await self.client.play_pause()
|
||||
|
||||
@command
|
||||
async def async_media_pause(self) -> None:
|
||||
"""Pause the current media."""
|
||||
controls = self.client.now_playing.controls
|
||||
|
@ -209,10 +199,12 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
|
|||
else:
|
||||
await self.client.pause()
|
||||
|
||||
@command
|
||||
async def async_media_stop(self) -> None:
|
||||
"""Stop the current media."""
|
||||
await self.client.stop()
|
||||
|
||||
@command
|
||||
async def async_media_play(self) -> None:
|
||||
"""Play the current media."""
|
||||
controls = self.client.now_playing.controls
|
||||
|
@ -224,14 +216,17 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
|
|||
else:
|
||||
await self.client.play()
|
||||
|
||||
@command
|
||||
async def async_media_next_track(self) -> None:
|
||||
"""Skip to the next track."""
|
||||
await self.client.next_track()
|
||||
|
||||
@command
|
||||
async def async_media_previous_track(self) -> None:
|
||||
"""Skip to the previous track."""
|
||||
await self.client.previous_track()
|
||||
|
||||
@command
|
||||
async def async_select_source(self, source: str) -> None:
|
||||
"""Select the source."""
|
||||
for src in self.client.sources:
|
||||
|
@ -239,34 +234,42 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
|
|||
await self.client.set_source_by_id(src.id)
|
||||
break
|
||||
|
||||
@command
|
||||
async def async_turn_on(self) -> None:
|
||||
"""Power on the device."""
|
||||
await self.client.power_on()
|
||||
|
||||
@command
|
||||
async def async_turn_off(self) -> None:
|
||||
"""Power off the device."""
|
||||
await self.client.power_off()
|
||||
|
||||
@command
|
||||
async def async_volume_up(self) -> None:
|
||||
"""Step the volume up."""
|
||||
await self.client.volume_up()
|
||||
|
||||
@command
|
||||
async def async_volume_down(self) -> None:
|
||||
"""Step the volume down."""
|
||||
await self.client.volume_down()
|
||||
|
||||
@command
|
||||
async def async_set_volume_level(self, volume: float) -> None:
|
||||
"""Set the volume level."""
|
||||
await self.client.set_volume(int(volume * 100))
|
||||
|
||||
@command
|
||||
async def async_mute_volume(self, mute: bool) -> None:
|
||||
"""Set the mute state."""
|
||||
await self.client.set_mute(mute)
|
||||
|
||||
@command
|
||||
async def async_media_seek(self, position: float) -> None:
|
||||
"""Seek to a position in the current media."""
|
||||
await self.client.media_seek(int(position))
|
||||
|
||||
@command
|
||||
async def async_set_shuffle(self, shuffle: bool) -> None:
|
||||
"""Set the shuffle mode for the current queue."""
|
||||
shuffle_mode = ShuffleMode.OFF
|
||||
|
@ -274,6 +277,7 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
|
|||
shuffle_mode = ShuffleMode.ALL
|
||||
await self.client.set_shuffle(shuffle_mode)
|
||||
|
||||
@command
|
||||
async def async_set_repeat(self, repeat: RepeatMode) -> None:
|
||||
"""Set the repeat mode for the current queue."""
|
||||
repeat_mode = CambridgeRepeatMode.OFF
|
||||
|
|
Loading…
Reference in New Issue