""" Home Assistant sensor processing """ import logging from typing import Any import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util import slugify from .const import ( CONF_API_LANGUAGE, CONF_CONFERENCE_ID, CONF_LEAGUE_ID, CONF_LEAGUE_PATH, CONF_SPORT_PATH, CONF_TEAM_ID, COORDINATOR, DEFAULT_CONFERENCE_ID, DEFAULT_ICON, DEFAULT_LEAGUE, DEFAULT_NAME, DEFAULT_SPORT_PATH, DOMAIN, ISSUE_URL, NATIVE_LEAGUES, OVERRIDE_DICT, SPORT_ICON_MAP, VERSION, ) from .coordinator import TeamTrackerCoordinator from .utils import load_file_overrides _LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_LEAGUE_ID, default=DEFAULT_LEAGUE): vol.All( vol.Upper, vol.In([*NATIVE_LEAGUES.keys(), "XXX"]) ), vol.Required(CONF_TEAM_ID): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_CONFERENCE_ID, default=DEFAULT_CONFERENCE_ID): cv.string, vol.Optional(CONF_API_LANGUAGE): cv.string, vol.Optional(CONF_SPORT_PATH): cv.string, vol.Optional(CONF_LEAGUE_PATH): cv.string, } ) async def async_setup_platform( hass: HomeAssistant, config: ConfigType, async_add_entities: AddEntitiesCallback, discovery_info=None, ) -> None: """Set up the sensor from the YAML Configuration""" sensor_name = config[CONF_NAME] _LOGGER.info( "%s: Setting up sensor from YAML using TeamTracker %s, if you have any issues please report them here: %s", sensor_name, VERSION, ISSUE_URL, ) league_ids = [*NATIVE_LEAGUES.keys(), "XXX"] try: vol.In(league_ids)(config[CONF_LEAGUE_ID]) except vol.Invalid: _LOGGER.warning("%s: `league_id` must be valid (one of %s)", sensor_name, league_ids) _LOGGER.error("%s: Support for invalid `league_id` in YAML was deprecated in v0.7.6. Correct config prior to next upgrade.", sensor_name) return # Raise an exception if the league ID is XXX and the sport or league path is not # specified if config[CONF_LEAGUE_ID] == "XXX" and not ( CONF_SPORT_PATH in config and CONF_LEAGUE_PATH in config ): error_msg = ( "Must specify sport and league path for custom league (league_id = XXX)" ) _LOGGER.warning("%s: %s", sensor_name, error_msg) return league_id = config[CONF_LEAGUE_ID].upper() # If the league ID is not in the map, it must be XXX and therefore we get the path # and league from the config config.update( NATIVE_LEAGUES.get( league_id, { k: v for k, v in config.items() if k in (CONF_SPORT_PATH, CONF_LEAGUE_PATH) }, ) ) if DOMAIN not in hass.data.keys(): hass.data.setdefault(DOMAIN, {}) # Load the OVERRIDE_DICT if it doesn't exist if OVERRIDE_DICT not in hass.data[DOMAIN]: hass.data[DOMAIN][OVERRIDE_DICT] = None override_dict = await hass.async_add_executor_job(load_file_overrides, hass) if OVERRIDE_DICT not in hass.data[DOMAIN] or hass.data[DOMAIN][OVERRIDE_DICT] is None: hass.data[DOMAIN][OVERRIDE_DICT] = override_dict # Setup the data coordinator coordinator = TeamTrackerCoordinator( hass, config, ) # Fetch initial data so we have data when entities subscribe await coordinator.async_refresh() # For YAML, use sensor name for index. Assumes sensor_name = entity_name hass.data[DOMAIN][sensor_name] = { COORDINATOR: coordinator, } async_add_entities([TeamTrackerScoresSensor(hass, None, config)], True) async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: """Setup sensors from a config entry created in the integrations UI.""" sensor_name = entry.data[CONF_NAME] _LOGGER.info( "%s: Updating sensor from UI using TeamTracker %s, if you have any issues please report them here: %s", sensor_name, VERSION, ISSUE_URL, ) config = hass.data[DOMAIN][entry.entry_id] # Update our config to include new repos and remove those that have been removed. if entry.options: config.update(entry.options) async_add_entities([TeamTrackerScoresSensor(hass, entry, None)], True) class TeamTrackerScoresSensor(CoordinatorEntity): """Representation of a Sensor.""" _unrecorded_attributes = frozenset({"last_update"}) def __init__(self, hass: HomeAssistant, entry: ConfigEntry, config: ConfigType) -> None: """Initialize the sensor.""" if entry is not None: # GUI setup, use entry_id as index entry_id = entry.entry_id sensor_coordinator = hass.data[DOMAIN][entry_id][COORDINATOR] super().__init__(sensor_coordinator) sport_path = entry.data.get(CONF_SPORT_PATH, DEFAULT_SPORT_PATH) sensor_name = entry.data[CONF_NAME] else: # YAML setup, use sensor_name as index (assumes sensor_name = entity_id) sensor_name = config[CONF_NAME] entry_id = slugify(f"{config.get(CONF_TEAM_ID)}") sensor_coordinator = hass.data[DOMAIN][sensor_name][COORDINATOR] super().__init__(sensor_coordinator) self._yaml_coordinator = sensor_coordinator # Store reference for cleanup try: sport_path = config[CONF_SPORT_PATH] except (KeyError, AttributeError): # pylint: disable=broad-exception-caught sport_path = DEFAULT_SPORT_PATH if sport_path == DEFAULT_SPORT_PATH: _LOGGER.debug( "%s: Initializing sensor values. SPORT_PATH not set.", sensor_name, ) icon = SPORT_ICON_MAP.get(sport_path, DEFAULT_ICON) if icon == DEFAULT_ICON: _LOGGER.debug( "%s: Initializing sensor values. Sport icon not found for sport '%s'", sensor_name, sport_path, ) self._entry_id = entry_id self._name = sensor_name self._icon = icon self.coordinator = sensor_coordinator @property def unique_id(self) -> str: """ Return a unique, Home Assistant friendly identifier for this entity. """ return f"{slugify(self._name)}_{self._entry_id}" @property def name(self) -> str: """Return the name of the sensor.""" return self._name @property def icon(self) -> str: """Return the icon to use in the frontend, if any.""" return self._icon @property def state(self) -> str | None: """Return the state of the sensor.""" if self.coordinator.data is None: return None return self.coordinator.data.state @property def extra_state_attributes(self) -> dict[str, Any]: """Return the state message.""" attrs: dict[str, Any] = {} if self.coordinator.data is None: return attrs attrs[ATTR_ATTRIBUTION] = self.coordinator.provider.ATTRIBUTION attrs.update(self.coordinator.data.to_dict_all_attr()) return attrs @property def available(self) -> bool: """Return if entity is available.""" return self.coordinator.last_update_success async def async_will_remove_from_hass(self) -> None: """Clean up when entity is being removed.""" # Only cleanup for YAML setup (entry is None) if hasattr(self, '_yaml_coordinator'): await self._yaml_coordinator.async_shutdown() _LOGGER.debug("%s: Cleaned up YAML coordinator", self._name)