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 Audio
pull/125837/head
Noah Husby 2024-09-12 10:09:53 -04:00 committed by GitHub
parent 4afc472068
commit a4c88a8591
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 56 additions and 13 deletions

View File

@ -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)

View File

@ -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