246 lines
8.1 KiB
Python
246 lines
8.1 KiB
Python
""" 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) |