"""Sensor platform for Hyperion.""" from __future__ import annotations import functools from typing import Any from hyperion import client from hyperion.const import ( KEY_COMPONENTID, KEY_ORIGIN, KEY_OWNER, KEY_PRIORITIES, KEY_PRIORITY, KEY_RGB, KEY_UPDATE, KEY_VALUE, KEY_VISIBLE, ) from homeassistant.components.sensor import SensorEntity, SensorEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ( get_hyperion_device_id, get_hyperion_unique_id, listen_for_instance_updates, ) from .const import ( CONF_INSTANCE_CLIENTS, DOMAIN, HYPERION_MANUFACTURER_NAME, HYPERION_MODEL_NAME, SIGNAL_ENTITY_REMOVE, TYPE_HYPERION_SENSOR_BASE, TYPE_HYPERION_SENSOR_VISIBLE_PRIORITY, ) SENSORS = [TYPE_HYPERION_SENSOR_VISIBLE_PRIORITY] PRIORITY_SENSOR_DESCRIPTION = SensorEntityDescription( key="visible_priority", translation_key="visible_priority", icon="mdi:lava-lamp", ) def _sensor_unique_id(server_id: str, instance_num: int, suffix: str) -> str: """Calculate a sensor's unique_id.""" return get_hyperion_unique_id( server_id, instance_num, f"{TYPE_HYPERION_SENSOR_BASE}_{suffix}", ) async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up a Hyperion platform from config entry.""" entry_data = hass.data[DOMAIN][config_entry.entry_id] server_id = config_entry.unique_id @callback def instance_add(instance_num: int, instance_name: str) -> None: """Add entities for a new Hyperion instance.""" assert server_id sensors = [ HyperionVisiblePrioritySensor( server_id, instance_num, instance_name, entry_data[CONF_INSTANCE_CLIENTS][instance_num], PRIORITY_SENSOR_DESCRIPTION, ) ] async_add_entities(sensors) @callback def instance_remove(instance_num: int) -> None: """Remove entities for an old Hyperion instance.""" assert server_id for sensor in SENSORS: async_dispatcher_send( hass, SIGNAL_ENTITY_REMOVE.format( _sensor_unique_id(server_id, instance_num, sensor), ), ) listen_for_instance_updates(hass, config_entry, instance_add, instance_remove) class HyperionSensor(SensorEntity): """Sensor class.""" _attr_has_entity_name = True _attr_should_poll = False def __init__( self, server_id: str, instance_num: int, instance_name: str, hyperion_client: client.HyperionClient, entity_description: SensorEntityDescription, ) -> None: """Initialize the sensor.""" self.entity_description = entity_description self._client = hyperion_client self._attr_native_value = None self._client_callbacks: dict[str, Any] = {} device_id = get_hyperion_device_id(server_id, instance_num) self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, device_id)}, manufacturer=HYPERION_MANUFACTURER_NAME, model=HYPERION_MODEL_NAME, name=instance_name, configuration_url=self._client.remote_url, ) @property def available(self) -> bool: """Return server availability.""" return bool(self._client.has_loaded_state) async def async_added_to_hass(self) -> None: """Register callbacks when entity added to hass.""" self.async_on_remove( async_dispatcher_connect( self.hass, SIGNAL_ENTITY_REMOVE.format(self._attr_unique_id), functools.partial(self.async_remove, force_remove=True), ) ) self._client.add_callbacks(self._client_callbacks) async def async_will_remove_from_hass(self) -> None: """Cleanup prior to hass removal.""" self._client.remove_callbacks(self._client_callbacks) class HyperionVisiblePrioritySensor(HyperionSensor): """Class that displays the visible priority of a Hyperion instance.""" def __init__( self, server_id: str, instance_num: int, instance_name: str, hyperion_client: client.HyperionClient, entity_description: SensorEntityDescription, ) -> None: """Initialize the sensor.""" super().__init__( server_id, instance_num, instance_name, hyperion_client, entity_description ) self._attr_unique_id = _sensor_unique_id( server_id, instance_num, TYPE_HYPERION_SENSOR_VISIBLE_PRIORITY ) self._client_callbacks = { f"{KEY_PRIORITIES}-{KEY_UPDATE}": self._update_priorities } @callback def _update_priorities(self, _: dict[str, Any] | None = None) -> None: """Update Hyperion priorities.""" state_value = None attrs = {} for priority in self._client.priorities or []: if not (KEY_VISIBLE in priority and priority[KEY_VISIBLE] is True): continue if priority[KEY_COMPONENTID] == "COLOR": state_value = priority[KEY_VALUE][KEY_RGB] else: state_value = priority[KEY_OWNER] attrs = { "component_id": priority[KEY_COMPONENTID], "origin": priority[KEY_ORIGIN], "priority": priority[KEY_PRIORITY], "owner": priority[KEY_OWNER], } if priority[KEY_COMPONENTID] == "COLOR": attrs["color"] = priority[KEY_VALUE] else: attrs["color"] = None self._attr_native_value = state_value self._attr_extra_state_attributes = attrs self.async_write_ha_state()