Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The Tile component."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass
6 from datetime import timedelta
7 from functools import partial
8 
9 from pytile import async_login
10 from pytile.errors import InvalidAuthError, SessionExpiredError, TileError
11 from pytile.tile import Tile
12 
13 from homeassistant.config_entries import ConfigEntry
14 from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
15 from homeassistant.core import HomeAssistant, callback
16 from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
17 from homeassistant.helpers import aiohttp_client
18 from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries
19 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
20 from homeassistant.util.async_ import gather_with_limited_concurrency
21 
22 from .const import DOMAIN, LOGGER
23 
24 PLATFORMS = [Platform.DEVICE_TRACKER]
25 DEVICE_TYPES = ["PHONE", "TILE"]
26 
27 DEFAULT_INIT_TASK_LIMIT = 2
28 DEFAULT_UPDATE_INTERVAL = timedelta(minutes=2)
29 
30 CONF_SHOW_INACTIVE = "show_inactive"
31 
32 
33 @dataclass
34 class TileData:
35  """Define an object to be stored in `hass.data`."""
36 
37  coordinators: dict[str, DataUpdateCoordinator[None]]
38  tiles: dict[str, Tile]
39 
40 
41 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
42  """Set up Tile as config entry."""
43 
44  @callback
45  def async_migrate_callback(entity_entry: RegistryEntry) -> dict | None:
46  """Define a callback to migrate appropriate Tile entities to new unique IDs.
47 
48  Old: tile_{uuid}
49  New: {username}_{uuid}
50  """
51  if entity_entry.unique_id.startswith(entry.data[CONF_USERNAME]):
52  return None
53 
54  new_unique_id = f"{entry.data[CONF_USERNAME]}_".join(
55  entity_entry.unique_id.split(f"{DOMAIN}_")
56  )
57 
58  LOGGER.debug(
59  "Migrating entity %s from old unique ID '%s' to new unique ID '%s'",
60  entity_entry.entity_id,
61  entity_entry.unique_id,
62  new_unique_id,
63  )
64 
65  return {"new_unique_id": new_unique_id}
66 
67  await async_migrate_entries(hass, entry.entry_id, async_migrate_callback)
68 
69  # Tile's API uses cookies to identify a consumer; in order to allow for multiple
70  # instances of this config entry, we use a new session each time:
71  websession = aiohttp_client.async_create_clientsession(hass)
72 
73  try:
74  client = await async_login(
75  entry.data[CONF_USERNAME],
76  entry.data[CONF_PASSWORD],
77  session=websession,
78  )
79  tiles = await client.async_get_tiles()
80  except InvalidAuthError as err:
81  raise ConfigEntryAuthFailed("Invalid credentials") from err
82  except TileError as err:
83  raise ConfigEntryNotReady("Error during integration setup") from err
84 
85  async def async_update_tile(tile: Tile) -> None:
86  """Update the Tile."""
87  try:
88  await tile.async_update()
89  except InvalidAuthError as err:
90  raise ConfigEntryAuthFailed("Invalid credentials") from err
91  except SessionExpiredError:
92  LOGGER.debug("Tile session expired; creating a new one")
93  await client.async_init()
94  except TileError as err:
95  raise UpdateFailed(f"Error while retrieving data: {err}") from err
96 
97  coordinators: dict[str, DataUpdateCoordinator[None]] = {}
98  coordinator_init_tasks = []
99 
100  for tile_uuid, tile in tiles.items():
101  coordinator = coordinators[tile_uuid] = DataUpdateCoordinator(
102  hass,
103  LOGGER,
104  config_entry=entry,
105  name=tile.name,
106  update_interval=DEFAULT_UPDATE_INTERVAL,
107  update_method=partial(async_update_tile, tile),
108  )
109  coordinator_init_tasks.append(coordinator.async_refresh())
110 
112  DEFAULT_INIT_TASK_LIMIT, *coordinator_init_tasks
113  )
114  hass.data.setdefault(DOMAIN, {})
115  hass.data[DOMAIN][entry.entry_id] = TileData(coordinators=coordinators, tiles=tiles)
116 
117  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
118 
119  return True
120 
121 
122 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
123  """Unload a Tile config entry."""
124  unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
125  if unload_ok:
126  hass.data[DOMAIN].pop(entry.entry_id)
127 
128  return unload_ok
None async_migrate_entries(HomeAssistant hass, dict[str, AdapterDetails] adapters, str default_adapter)
Definition: __init__.py:249
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:41
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:122
Any gather_with_limited_concurrency(int limit, *Any tasks, bool return_exceptions=False)
Definition: async_.py:103