Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The yolink integration."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from dataclasses import dataclass
7 from datetime import timedelta
8 from typing import Any
9 
10 from yolink.const import ATTR_DEVICE_SMART_REMOTER
11 from yolink.device import YoLinkDevice
12 from yolink.exception import YoLinkAuthFailError, YoLinkClientError
13 from yolink.home_manager import YoLinkHome
14 from yolink.message_listener import MessageListener
15 
16 from homeassistant.config_entries import ConfigEntry
17 from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform
18 from homeassistant.core import HomeAssistant
19 from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
20 from homeassistant.helpers import (
21  aiohttp_client,
22  config_entry_oauth2_flow,
23  config_validation as cv,
24  device_registry as dr,
25 )
26 from homeassistant.helpers.typing import ConfigType
27 
28 from . import api
29 from .const import DOMAIN, YOLINK_EVENT
30 from .coordinator import YoLinkCoordinator
31 from .device_trigger import CONF_LONG_PRESS, CONF_SHORT_PRESS
32 from .services import async_register_services
33 
34 SCAN_INTERVAL = timedelta(minutes=5)
35 
36 CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
37 
38 
39 PLATFORMS = [
40  Platform.BINARY_SENSOR,
41  Platform.CLIMATE,
42  Platform.COVER,
43  Platform.LIGHT,
44  Platform.LOCK,
45  Platform.NUMBER,
46  Platform.SENSOR,
47  Platform.SIREN,
48  Platform.SWITCH,
49  Platform.VALVE,
50 ]
51 
52 
53 class YoLinkHomeMessageListener(MessageListener):
54  """YoLink home message listener."""
55 
56  def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
57  """Init YoLink home message listener."""
58  self._hass_hass = hass
59  self._entry_entry = entry
60 
61  def on_message(self, device: YoLinkDevice, msg_data: dict[str, Any]) -> None:
62  """On YoLink home message received."""
63  entry_data = self._hass_hass.data[DOMAIN].get(self._entry_entry.entry_id)
64  if not entry_data:
65  return
66  device_coordinators = entry_data.device_coordinators
67  if not device_coordinators:
68  return
69  device_coordinator: YoLinkCoordinator = device_coordinators.get(
70  device.device_id
71  )
72  if device_coordinator is None:
73  return
74  device_coordinator.dev_online = True
75  device_coordinator.async_set_updated_data(msg_data)
76  # handling events
77  if (
78  device_coordinator.device.device_type == ATTR_DEVICE_SMART_REMOTER
79  and msg_data.get("event") is not None
80  ):
81  device_registry = dr.async_get(self._hass_hass)
82  device_entry = device_registry.async_get_device(
83  identifiers={(DOMAIN, device_coordinator.device.device_id)}
84  )
85  if device_entry is None:
86  return
87  key_press_type = None
88  if msg_data["event"]["type"] == "Press":
89  key_press_type = CONF_SHORT_PRESS
90  else:
91  key_press_type = CONF_LONG_PRESS
92  button_idx = msg_data["event"]["keyMask"]
93  event_data = {
94  "type": f"button_{button_idx}_{key_press_type}",
95  "device_id": device_entry.id,
96  }
97  self._hass_hass.bus.async_fire(YOLINK_EVENT, event_data)
98 
99 
100 @dataclass
102  """YoLink home store."""
103 
104  home_instance: YoLinkHome
105  device_coordinators: dict[str, YoLinkCoordinator]
106 
107 
108 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
109  """Set up YoLink."""
110 
112 
113  return True
114 
115 
116 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
117  """Set up yolink from a config entry."""
118  hass.data.setdefault(DOMAIN, {})
119  implementation = (
120  await config_entry_oauth2_flow.async_get_config_entry_implementation(
121  hass, entry
122  )
123  )
124 
125  session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)
126 
127  auth_mgr = api.ConfigEntryAuth(
128  hass, aiohttp_client.async_get_clientsession(hass), session
129  )
130  yolink_home = YoLinkHome()
131  try:
132  async with asyncio.timeout(10):
133  await yolink_home.async_setup(
134  auth_mgr, YoLinkHomeMessageListener(hass, entry)
135  )
136  except YoLinkAuthFailError as yl_auth_err:
137  raise ConfigEntryAuthFailed from yl_auth_err
138  except (YoLinkClientError, TimeoutError) as err:
139  raise ConfigEntryNotReady from err
140 
141  device_coordinators = {}
142 
143  # revese mapping
144  device_pairing_mapping = {}
145  for device in yolink_home.get_devices():
146  if (parent_id := device.get_paired_device_id()) is not None:
147  device_pairing_mapping[parent_id] = device.device_id
148 
149  for device in yolink_home.get_devices():
150  paried_device: YoLinkDevice | None = None
151  if (
152  paried_device_id := device_pairing_mapping.get(device.device_id)
153  ) is not None:
154  paried_device = yolink_home.get_device(paried_device_id)
155  device_coordinator = YoLinkCoordinator(hass, device, paried_device)
156  try:
157  await device_coordinator.async_config_entry_first_refresh()
158  except ConfigEntryNotReady:
159  # Not failure by fetching device state
160  device_coordinator.data = {}
161  device_coordinators[device.device_id] = device_coordinator
162  hass.data[DOMAIN][entry.entry_id] = YoLinkHomeStore(
163  yolink_home, device_coordinators
164  )
165  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
166 
167  async def async_yolink_unload(event) -> None:
168  """Unload yolink."""
169  await yolink_home.async_unload()
170 
171  entry.async_on_unload(
172  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_yolink_unload)
173  )
174 
175  return True
176 
177 
178 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
179  """Unload a config entry."""
180  if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
181  await hass.data[DOMAIN][entry.entry_id].home_instance.async_unload()
182  hass.data[DOMAIN].pop(entry.entry_id)
183  return unload_ok
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
None async_register_services(HomeAssistant hass)
Definition: services.py:80