core/homeassistant/components/spotify/sensor.py

180 lines
5.9 KiB
Python

"""Sensor platform for Spotify."""
from collections.abc import Callable
from dataclasses import dataclass
from spotifyaio.models import AudioFeatures, Key
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .coordinator import SpotifyConfigEntry, SpotifyCoordinator
from .entity import SpotifyEntity
@dataclass(frozen=True, kw_only=True)
class SpotifyAudioFeaturesSensorEntityDescription(SensorEntityDescription):
"""Describes Spotify sensor entity."""
value_fn: Callable[[AudioFeatures], float | str | None]
KEYS: dict[Key, str] = {
Key.C: "C",
Key.C_SHARP_D_FLAT: "C♯/D♭",
Key.D: "D",
Key.D_SHARP_E_FLAT: "D♯/E♭",
Key.E: "E",
Key.F: "F",
Key.F_SHARP_G_FLAT: "F♯/G♭",
Key.G: "G",
Key.G_SHARP_A_FLAT: "G♯/A♭",
Key.A: "A",
Key.A_SHARP_B_FLAT: "A♯/B♭",
Key.B: "B",
}
KEY_OPTIONS = list(KEYS.values())
def _get_key(audio_features: AudioFeatures) -> str | None:
if audio_features.key is None:
return None
return KEYS[audio_features.key]
AUDIO_FEATURE_SENSORS: tuple[SpotifyAudioFeaturesSensorEntityDescription, ...] = (
SpotifyAudioFeaturesSensorEntityDescription(
key="bpm",
translation_key="song_tempo",
native_unit_of_measurement="bpm",
suggested_display_precision=0,
value_fn=lambda audio_features: audio_features.tempo,
),
SpotifyAudioFeaturesSensorEntityDescription(
key="danceability",
translation_key="danceability",
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=0,
value_fn=lambda audio_features: audio_features.danceability * 100,
entity_registry_enabled_default=False,
),
SpotifyAudioFeaturesSensorEntityDescription(
key="energy",
translation_key="energy",
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=0,
value_fn=lambda audio_features: audio_features.energy * 100,
entity_registry_enabled_default=False,
),
SpotifyAudioFeaturesSensorEntityDescription(
key="mode",
translation_key="mode",
device_class=SensorDeviceClass.ENUM,
options=["major", "minor"],
value_fn=lambda audio_features: audio_features.mode.name.lower(),
entity_registry_enabled_default=False,
),
SpotifyAudioFeaturesSensorEntityDescription(
key="speechiness",
translation_key="speechiness",
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=0,
value_fn=lambda audio_features: audio_features.speechiness * 100,
entity_registry_enabled_default=False,
),
SpotifyAudioFeaturesSensorEntityDescription(
key="acousticness",
translation_key="acousticness",
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=0,
value_fn=lambda audio_features: audio_features.acousticness * 100,
entity_registry_enabled_default=False,
),
SpotifyAudioFeaturesSensorEntityDescription(
key="instrumentalness",
translation_key="instrumentalness",
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=0,
value_fn=lambda audio_features: audio_features.instrumentalness * 100,
entity_registry_enabled_default=False,
),
SpotifyAudioFeaturesSensorEntityDescription(
key="liveness",
translation_key="liveness",
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=0,
value_fn=lambda audio_features: audio_features.liveness * 100,
entity_registry_enabled_default=False,
),
SpotifyAudioFeaturesSensorEntityDescription(
key="valence",
translation_key="valence",
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=0,
value_fn=lambda audio_features: audio_features.valence * 100,
entity_registry_enabled_default=False,
),
SpotifyAudioFeaturesSensorEntityDescription(
key="time_signature",
translation_key="time_signature",
device_class=SensorDeviceClass.ENUM,
options=["3/4", "4/4", "5/4", "6/4", "7/4"],
value_fn=lambda audio_features: f"{audio_features.time_signature}/4",
entity_registry_enabled_default=False,
),
SpotifyAudioFeaturesSensorEntityDescription(
key="key",
translation_key="key",
device_class=SensorDeviceClass.ENUM,
options=KEY_OPTIONS,
value_fn=_get_key,
entity_registry_enabled_default=False,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: SpotifyConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Spotify sensor based on a config entry."""
coordinator = entry.runtime_data.coordinator
async_add_entities(
SpotifyAudioFeatureSensor(coordinator, description)
for description in AUDIO_FEATURE_SENSORS
)
class SpotifyAudioFeatureSensor(SpotifyEntity, SensorEntity):
"""Representation of a Spotify sensor."""
entity_description: SpotifyAudioFeaturesSensorEntityDescription
def __init__(
self,
coordinator: SpotifyCoordinator,
entity_description: SpotifyAudioFeaturesSensorEntityDescription,
) -> None:
"""Initialize."""
super().__init__(coordinator)
self._attr_unique_id = (
f"{coordinator.current_user.user_id}_{entity_description.key}"
)
self.entity_description = entity_description
@property
def native_value(self) -> float | str | None:
"""Return the state of the sensor."""
if (audio_features := self.coordinator.data.audio_features) is None:
return None
return self.entity_description.value_fn(audio_features)