Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for Ring Doorbell/Chimes."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass
6 import logging
7 from typing import Any, cast
8 import uuid
9 
10 from ring_doorbell import Auth, Ring, RingDevices
11 
12 from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
13 from homeassistant.config_entries import ConfigEntry
14 from homeassistant.const import APPLICATION_NAME, CONF_DEVICE_ID, CONF_TOKEN
15 from homeassistant.core import HomeAssistant, callback
16 from homeassistant.helpers import device_registry as dr, entity_registry as er
17 from homeassistant.helpers.aiohttp_client import async_get_clientsession
18 
19 from .const import CONF_LISTEN_CREDENTIALS, DOMAIN, PLATFORMS
20 from .coordinator import RingDataCoordinator, RingListenCoordinator
21 
22 _LOGGER = logging.getLogger(__name__)
23 
24 
25 @dataclass
26 class RingData:
27  """Class to support type hinting of ring data collection."""
28 
29  api: Ring
30  devices: RingDevices
31  devices_coordinator: RingDataCoordinator
32  listen_coordinator: RingListenCoordinator
33 
34 
35 type RingConfigEntry = ConfigEntry[RingData]
36 
37 
38 def get_auth_user_agent() -> str:
39  """Return user-agent for Auth instantiation.
40 
41  user_agent will be the display name in the ring.com authorised devices.
42  """
43  return f"{APPLICATION_NAME}/{DOMAIN}-integration"
44 
45 
46 async def async_setup_entry(hass: HomeAssistant, entry: RingConfigEntry) -> bool:
47  """Set up a config entry."""
48 
49  def token_updater(token: dict[str, Any]) -> None:
50  """Handle from async context when token is updated."""
51  hass.config_entries.async_update_entry(
52  entry,
53  data={**entry.data, CONF_TOKEN: token},
54  )
55 
56  def listen_credentials_updater(token: dict[str, Any]) -> None:
57  """Handle from async context when token is updated."""
58  hass.config_entries.async_update_entry(
59  entry,
60  data={**entry.data, CONF_LISTEN_CREDENTIALS: token},
61  )
62 
63  user_agent = get_auth_user_agent()
64  client_session = async_get_clientsession(hass)
65  auth = Auth(
66  user_agent,
67  entry.data[CONF_TOKEN],
68  token_updater,
69  hardware_id=entry.data[CONF_DEVICE_ID],
70  http_client_session=client_session,
71  )
72  ring = Ring(auth)
73 
74  devices_coordinator = RingDataCoordinator(hass, ring)
75  listen_credentials = entry.data.get(CONF_LISTEN_CREDENTIALS)
76  listen_coordinator = RingListenCoordinator(
77  hass, ring, listen_credentials, listen_credentials_updater
78  )
79 
80  await devices_coordinator.async_config_entry_first_refresh()
81 
82  entry.runtime_data = RingData(
83  api=ring,
84  devices=ring.devices(),
85  devices_coordinator=devices_coordinator,
86  listen_coordinator=listen_coordinator,
87  )
88 
89  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
90 
91  return True
92 
93 
94 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
95  """Unload Ring entry."""
96  return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
97 
98 
100  hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
101 ) -> bool:
102  """Remove a config entry from a device."""
103  return True
104 
105 
106 async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
107  """Migrate old config entry."""
108  entry_version = entry.version
109  entry_minor_version = entry.minor_version
110  entry_id = entry.entry_id
111 
112  new_minor_version = 2
113  if entry_version == 1 and entry_minor_version == 1:
114  _LOGGER.debug(
115  "Migrating from version %s.%s", entry_version, entry_minor_version
116  )
117  # Migrate non-str unique ids
118  # This step used to run unconditionally from async_setup_entry
119  entity_registry = er.async_get(hass)
120 
121  @callback
122  def _async_str_unique_id_migrator(
123  entity_entry: er.RegistryEntry,
124  ) -> dict[str, str] | None:
125  # Old format for camera and light was int
126  unique_id = cast(str | int, entity_entry.unique_id)
127  if isinstance(unique_id, int):
128  new_unique_id = str(unique_id)
129  if existing_entity_id := entity_registry.async_get_entity_id(
130  entity_entry.domain, entity_entry.platform, new_unique_id
131  ):
132  _LOGGER.error(
133  "Cannot migrate to unique_id '%s', already exists for '%s', "
134  "You may have to delete unavailable ring entities",
135  new_unique_id,
136  existing_entity_id,
137  )
138  return None
139  _LOGGER.debug("Fixing non string unique id %s", entity_entry.unique_id)
140  return {"new_unique_id": new_unique_id}
141  return None
142 
143  await er.async_migrate_entries(hass, entry_id, _async_str_unique_id_migrator)
144 
145  # Migrate the hardware id
146  hardware_id = str(uuid.uuid4())
147  hass.config_entries.async_update_entry(
148  entry,
149  data={**entry.data, CONF_DEVICE_ID: hardware_id},
150  minor_version=new_minor_version,
151  )
152  _LOGGER.debug(
153  "Migration to version %s.%s complete", entry_version, new_minor_version
154  )
155 
156  entry_minor_version = entry.minor_version
157  new_minor_version = 3
158  if entry_version == 1 and entry_minor_version == 2:
159  _LOGGER.debug(
160  "Migrating from version %s.%s", entry_version, entry_minor_version
161  )
162 
163  @callback
164  def _async_camera_unique_id_migrator(
165  entity_entry: er.RegistryEntry,
166  ) -> dict[str, str] | None:
167  # Migrate camera unique ids to append -last
168  if entity_entry.domain == CAMERA_DOMAIN and not isinstance(
169  cast(str | int, entity_entry.unique_id), int
170  ):
171  new_unique_id = f"{entity_entry.unique_id}-last_recording"
172  return {"new_unique_id": new_unique_id}
173  return None
174 
175  await er.async_migrate_entries(hass, entry_id, _async_camera_unique_id_migrator)
176 
177  hass.config_entries.async_update_entry(
178  entry,
179  minor_version=new_minor_version,
180  )
181  _LOGGER.debug(
182  "Migration to version %s.%s complete", entry_version, new_minor_version
183  )
184 
185  return True
bool async_migrate_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:106
bool async_remove_config_entry_device(HomeAssistant hass, ConfigEntry config_entry, dr.DeviceEntry device_entry)
Definition: __init__.py:101
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:94
bool async_setup_entry(HomeAssistant hass, RingConfigEntry entry)
Definition: __init__.py:46
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)