Files
HomeAssistance/custom_components/teamtracker/__init__.py
T
2026-06-05 22:34:31 -04:00

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