Files
alexei.dolgolyov 705616f8b0 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).
2026-06-08 15:59:42 +03:00

59 lines
1.9 KiB
Python

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