feat(playlists): expose scene playlists as Home Assistant entities
One device per scene playlist (model "Scene Playlist"), each with: - a switch (start/stop; the server cycles one playlist at a time, so starting one stops any other) - a "Current Scene" sensor that resolves the active preset name from scene_presets while this playlist is cycling - an "Items" diagnostic sensor (configured scene count; no state_class) The coordinator reads scene_playlists plus the flat playlist_state from the /api/v1/snapshot payload and gains start_playlist()/stop_playlist(); __init__ registers and prunes per-playlist devices and reloads on playlist-id changes; the event listener also refreshes on the playlist_state_changed WS event. Shared device/lookup/running-state plumbing lives in a new LedGrabPlaylistEntity base (entity.py) used by both the switch and the sensors. Adds en/ru translation keys. Note: this also lands the in-progress coordinator migration from the per-request fan-out to the single /api/v1/snapshot poll that was already present in the working tree. Requires the led-grab server /api/v1/snapshot to emit scene_playlists + playlist_state (companion server change, tracked separately).
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
"""Shared base entity for scene-playlist platforms.
|
||||
|
||||
The playlist switch and the playlist stats sensors live on the same device and
|
||||
share the same lookup, availability, and running-state logic. Keeping that in
|
||||
one place means the snapshot key names (``scene_playlists`` / ``playlist_state``)
|
||||
and the "is this the active playlist?" match only ever live once, so the switch
|
||||
and sensors can't silently drift apart.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LedGrabCoordinator
|
||||
|
||||
|
||||
class LedGrabPlaylistEntity(CoordinatorEntity):
|
||||
"""Common plumbing for entities attached to a scene-playlist device."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: LedGrabCoordinator,
|
||||
playlist_id: str,
|
||||
entry_id: str,
|
||||
) -> None:
|
||||
super().__init__(coordinator)
|
||||
self._playlist_id = playlist_id
|
||||
self._entry_id = entry_id
|
||||
|
||||
@property
|
||||
def device_info(self) -> dict[str, Any]:
|
||||
return {"identifiers": {(DOMAIN, self._playlist_id)}}
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
return self._get_playlist() is not None
|
||||
|
||||
def _get_playlist(self) -> dict[str, Any] | None:
|
||||
if not self.coordinator.data:
|
||||
return None
|
||||
for playlist in self.coordinator.data.get("scene_playlists", []):
|
||||
if playlist.get("id") == self._playlist_id:
|
||||
return playlist
|
||||
return None
|
||||
|
||||
def _running_state(self) -> dict[str, Any] | None:
|
||||
"""Return the global cycling state if this playlist is the active one."""
|
||||
if not self.coordinator.data:
|
||||
return None
|
||||
state = self.coordinator.data.get("playlist_state") or {}
|
||||
if state.get("is_running") and state.get("playlist_id") == self._playlist_id:
|
||||
return state
|
||||
return None
|
||||
Reference in New Issue
Block a user