244 lines
8.2 KiB
Python
244 lines
8.2 KiB
Python
""" TeamTracker Team Status """
|
|
import asyncio
|
|
from datetime import date, datetime, timedelta, timezone
|
|
import json
|
|
import locale
|
|
import logging
|
|
import os
|
|
import re
|
|
from typing import ClassVar
|
|
|
|
import aiofiles
|
|
import arrow
|
|
from async_timeout import timeout
|
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import CONF_NAME
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|
from homeassistant.helpers.entity_registry import ( # pylint: disable=reimported
|
|
async_entries_for_config_entry,
|
|
async_get,
|
|
async_get as async_get_entity_registry,
|
|
)
|
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
|
|
|
from .const import (
|
|
API_LIMIT,
|
|
CONF_API_LANGUAGE,
|
|
CONF_CONFERENCE_ID,
|
|
CONF_LEAGUE_ID,
|
|
CONF_LEAGUE_PATH,
|
|
CONF_SPORT_PATH,
|
|
CONF_TEAM_ID,
|
|
COORDINATOR,
|
|
DEFAULT_KICKOFF_IN,
|
|
DEFAULT_LAST_UPDATE,
|
|
DEFAULT_LEAGUE,
|
|
DEFAULT_LOGO,
|
|
DEFAULT_TIMEOUT,
|
|
DOMAIN,
|
|
ISSUE_URL,
|
|
NATIVE_LEAGUES,
|
|
OVERRIDE_DICT,
|
|
PLATFORMS,
|
|
SERVICE_NAME_CALL_API,
|
|
SERVICE_NAME_RELOAD_OVERRIDES,
|
|
VERSION,
|
|
)
|
|
from .coordinator import TeamTrackerCoordinator
|
|
from .provider_base import BaseSportProvider
|
|
from .utils import has_team, is_integer, load_file_overrides
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Load the saved entities."""
|
|
|
|
|
|
async def get_entry_id_from_entity_id(hass: HomeAssistant, entity_id: str):
|
|
"""Retrieve entry_id from entity_id."""
|
|
# Get the entity registry
|
|
entity_registry = async_get_entity_registry(hass)
|
|
|
|
# Find the entry associated with the given entity_id
|
|
entry = entity_registry.async_get(entity_id)
|
|
|
|
if entry:
|
|
return entry.config_entry_id
|
|
|
|
return None
|
|
|
|
|
|
#
|
|
# async_call_api_service()
|
|
# Service to change the path, league, team and conference
|
|
#
|
|
async def async_call_api_service(call):
|
|
"""Handle the service action call."""
|
|
|
|
sport_path = str(call.data.get(CONF_SPORT_PATH, "football"))
|
|
league_path = str(call.data.get(CONF_LEAGUE_PATH, "nfl"))
|
|
team_id = str(call.data.get(CONF_TEAM_ID, "cle"))
|
|
conference_id = call.data.get(CONF_CONFERENCE_ID, "")
|
|
conference_id = "" if conference_id is None else str(conference_id)
|
|
entity_ids = call.data.get("entity_id", "none")
|
|
|
|
for entity_id in entity_ids:
|
|
entry_id = await get_entry_id_from_entity_id(hass, entity_id)
|
|
|
|
if entry_id: # Set up from UI, use entry_id as index
|
|
sensor_coordinator = hass.data[DOMAIN][entry_id][COORDINATOR]
|
|
sensor_coordinator.update_team_info(sport_path, league_path, team_id, conference_id)
|
|
await sensor_coordinator.async_refresh()
|
|
else: # Set up from YAML, use sensor_name (from entity_name) as index
|
|
sensor_name = entity_id.split('.')[-1]
|
|
if sensor_name in hass.data[DOMAIN] and COORDINATOR in hass.data[DOMAIN][sensor_name]:
|
|
sensor_coordinator = hass.data[DOMAIN][sensor_name][COORDINATOR]
|
|
sensor_coordinator.update_team_info(sport_path, league_path, team_id, conference_id)
|
|
await sensor_coordinator.async_refresh()
|
|
else: # YAML had duplicate names so it doesn't match the entity_name
|
|
_LOGGER.info(
|
|
"%s: [service=call_api] No entry_id found (likely because of non-unique sensor names in YAML) for entity_id: %s",
|
|
sensor_name,
|
|
entity_id,
|
|
)
|
|
|
|
#
|
|
# async_reload_overrides()
|
|
# Service to reload the override files
|
|
#
|
|
async def async_reload_overrides(call):
|
|
"""Handle the service action call to reload the override file."""
|
|
|
|
_LOGGER.warning(
|
|
"Reloading local teamtracker_overrides.json file. All TeamTracker sensors will be impacted on next API call."
|
|
)
|
|
|
|
# Initialize DOMAIN in hass.data if it doesn't exist
|
|
if DOMAIN not in hass.data:
|
|
hass.data[DOMAIN] = {}
|
|
|
|
# Reload the OVERRIDE_DICT
|
|
override_dict = await hass.async_add_executor_job(load_file_overrides, hass)
|
|
hass.data[DOMAIN][OVERRIDE_DICT] = override_dict
|
|
|
|
# Print startup message
|
|
|
|
sensor_name = entry.data[CONF_NAME]
|
|
|
|
_LOGGER.info(
|
|
"%s: Setting up sensor from UI configuration using TeamTracker %s, if you have any issues please report them here: %s",
|
|
sensor_name,
|
|
VERSION,
|
|
ISSUE_URL,
|
|
)
|
|
|
|
# Initialize DOMAIN in hass.data if it doesn't exist
|
|
if DOMAIN not in hass.data:
|
|
hass.data[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
|
|
|
|
entry.async_on_unload(entry.add_update_listener(update_options_listener))
|
|
|
|
if entry.unique_id is not None:
|
|
_LOGGER.info(
|
|
"%s: async_setup_entry() - entry.unique_id is not None: %s",
|
|
sensor_name,
|
|
entry.unique_id,
|
|
)
|
|
hass.config_entries.async_update_entry(entry, unique_id=None)
|
|
|
|
ent_reg = async_get(hass)
|
|
for entity in async_entries_for_config_entry(ent_reg, entry.entry_id):
|
|
ent_reg.async_update_entity(entity.entity_id, new_unique_id=entry.entry_id)
|
|
|
|
# Setup the data coordinator
|
|
coordinator = TeamTrackerCoordinator(
|
|
hass, entry.data, entry
|
|
)
|
|
|
|
# Fetch initial data so we have data when entities subscribe
|
|
# await coordinator.async_refresh()
|
|
|
|
# For UI, use entry_id as index
|
|
hass.data[DOMAIN][entry.entry_id] = {
|
|
COORDINATOR: coordinator,
|
|
}
|
|
|
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
|
#
|
|
# Register services for sensor
|
|
#
|
|
hass.services.async_register(DOMAIN, SERVICE_NAME_CALL_API, async_call_api_service,)
|
|
hass.services.async_register(DOMAIN, SERVICE_NAME_RELOAD_OVERRIDES, async_reload_overrides,)
|
|
|
|
return True
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Handle removal of an entry."""
|
|
|
|
# Unload platforms
|
|
unload_ok = all(
|
|
await asyncio.gather(
|
|
*[
|
|
hass.config_entries.async_forward_entry_unload(entry, platform)
|
|
for platform in PLATFORMS
|
|
]
|
|
)
|
|
)
|
|
|
|
if unload_ok:
|
|
domain_data = hass.data.get(DOMAIN, None)
|
|
if domain_data and entry.entry_id in domain_data:
|
|
domain_data.pop(entry.entry_id)
|
|
|
|
# Only remove service if this is the last entry
|
|
if not domain_data:
|
|
hass.services.async_remove(DOMAIN, SERVICE_NAME_CALL_API)
|
|
|
|
return unload_ok
|
|
|
|
|
|
#
|
|
# Only needed if Options Flow is added
|
|
#
|
|
async def update_options_listener(hass, entry):
|
|
"""Update listener."""
|
|
|
|
await hass.config_entries.async_reload(entry.entry_id)
|
|
|
|
|
|
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Migrate an old config entry."""
|
|
sensor_name = entry.data[CONF_NAME]
|
|
version = entry.version
|
|
|
|
# 1-> 2->3: Migration format
|
|
# Add CONF_LEAGUE_ID, CONF_SPORT_PATH, and CONF_LEAGUE_PATH if not already populated
|
|
if version < 3:
|
|
_LOGGER.debug("%s: Migrating from version %s", sensor_name, version)
|
|
updated_config = entry.data.copy()
|
|
|
|
if CONF_LEAGUE_ID not in updated_config.keys():
|
|
updated_config[CONF_LEAGUE_ID] = DEFAULT_LEAGUE
|
|
if (CONF_SPORT_PATH not in updated_config.keys()) or (
|
|
CONF_LEAGUE_PATH not in updated_config.keys()
|
|
):
|
|
league_id = updated_config[CONF_LEAGUE_ID].upper()
|
|
updated_config.update(NATIVE_LEAGUES[league_id])
|
|
|
|
if updated_config != entry.data:
|
|
hass.config_entries.async_update_entry(entry, data=updated_config, version=3)
|
|
|
|
_LOGGER.debug("%s: Migration to version %s complete", sensor_name, entry.version)
|
|
|
|
return True |