Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for Vera devices."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from collections import defaultdict
7 import logging
8 
9 import pyvera as veraApi
10 from requests.exceptions import RequestException
11 import voluptuous as vol
12 
13 from homeassistant import config_entries
14 from homeassistant.config_entries import ConfigEntry
15 from homeassistant.const import (
16  CONF_EXCLUDE,
17  CONF_LIGHTS,
18  EVENT_HOMEASSISTANT_STOP,
19  Platform,
20 )
21 from homeassistant.core import HomeAssistant
22 from homeassistant.exceptions import ConfigEntryNotReady
23 from homeassistant.helpers import config_validation as cv
24 from homeassistant.helpers.typing import ConfigType
25 
26 from .common import (
27  ControllerData,
28  SubscriptionRegistry,
29  get_configured_platforms,
30  get_controller_data,
31  set_controller_data,
32 )
33 from .config_flow import fix_device_id_list, new_options
34 from .const import CONF_CONTROLLER, DOMAIN
35 
36 _LOGGER = logging.getLogger(__name__)
37 
38 VERA_ID_LIST_SCHEMA = vol.Schema([int])
39 
40 CONFIG_SCHEMA = vol.Schema(
41  vol.All(
42  cv.deprecated(DOMAIN),
43  {
44  DOMAIN: vol.Schema(
45  {
46  vol.Required(CONF_CONTROLLER): cv.url,
47  vol.Optional(CONF_EXCLUDE, default=[]): VERA_ID_LIST_SCHEMA,
48  vol.Optional(CONF_LIGHTS, default=[]): VERA_ID_LIST_SCHEMA,
49  }
50  )
51  },
52  ),
53  extra=vol.ALLOW_EXTRA,
54 )
55 
56 
57 async def async_setup(hass: HomeAssistant, base_config: ConfigType) -> bool:
58  """Set up for Vera controllers."""
59  hass.data[DOMAIN] = {}
60 
61  if not (config := base_config.get(DOMAIN)):
62  return True
63 
64  hass.async_create_task(
65  hass.config_entries.flow.async_init(
66  DOMAIN,
67  context={"source": config_entries.SOURCE_IMPORT},
68  data=config,
69  )
70  )
71 
72  return True
73 
74 
75 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
76  """Do setup of vera."""
77  # Use options entered during initial config flow or provided from configuration.yml
78  if entry.data.get(CONF_LIGHTS) or entry.data.get(CONF_EXCLUDE):
79  hass.config_entries.async_update_entry(
80  entry=entry,
81  data=entry.data,
82  options=new_options(
83  entry.data.get(CONF_LIGHTS, []),
84  entry.data.get(CONF_EXCLUDE, []),
85  ),
86  )
87 
88  saved_light_ids = entry.options.get(CONF_LIGHTS, [])
89  saved_exclude_ids = entry.options.get(CONF_EXCLUDE, [])
90 
91  base_url = entry.data[CONF_CONTROLLER]
92  light_ids = fix_device_id_list(saved_light_ids)
93  exclude_ids = fix_device_id_list(saved_exclude_ids)
94 
95  # If the ids were corrected. Update the config entry.
96  if light_ids != saved_light_ids or exclude_ids != saved_exclude_ids:
97  hass.config_entries.async_update_entry(
98  entry=entry, options=new_options(light_ids, exclude_ids)
99  )
100 
101  # Initialize the Vera controller.
102  subscription_registry = SubscriptionRegistry(hass)
103  controller = veraApi.VeraController(base_url, subscription_registry)
104 
105  try:
106  all_devices = await hass.async_add_executor_job(controller.get_devices)
107 
108  all_scenes = await hass.async_add_executor_job(controller.get_scenes)
109  except RequestException as exception:
110  # There was a network related error connecting to the Vera controller.
111  _LOGGER.exception("Error communicating with Vera API")
112  raise ConfigEntryNotReady from exception
113 
114  # Exclude devices unwanted by user.
115  devices = [device for device in all_devices if device.device_id not in exclude_ids]
116 
117  vera_devices: defaultdict[Platform, list[veraApi.VeraDevice]] = defaultdict(list)
118  for device in devices:
119  device_type = map_vera_device(device, light_ids)
120  if device_type is not None:
121  vera_devices[device_type].append(device)
122 
123  controller_data = ControllerData(
124  controller=controller,
125  devices=vera_devices,
126  scenes=all_scenes,
127  config_entry=entry,
128  )
129 
130  set_controller_data(hass, entry, controller_data)
131 
132  # Forward the config data to the necessary platforms.
133  await hass.config_entries.async_forward_entry_setups(
134  entry, platforms=get_configured_platforms(controller_data)
135  )
136 
137  def stop_subscription(event):
138  """Stop SubscriptionRegistry updates."""
139  controller.stop()
140 
141  await hass.async_add_executor_job(controller.start)
142  entry.async_on_unload(
143  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
144  )
145 
146  entry.async_on_unload(entry.add_update_listener(_async_update_listener))
147  return True
148 
149 
150 async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
151  """Unload vera config entry."""
152  controller_data: ControllerData = get_controller_data(hass, config_entry)
153  await asyncio.gather(
154  *(
155  hass.config_entries.async_unload_platforms(
156  config_entry, get_configured_platforms(controller_data)
157  ),
158  hass.async_add_executor_job(controller_data.controller.stop),
159  )
160  )
161  return True
162 
163 
164 async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
165  """Handle options update."""
166  await hass.config_entries.async_reload(entry.entry_id)
167 
168 
170  vera_device: veraApi.VeraDevice, remap: list[int]
171 ) -> Platform | None:
172  """Map vera classes to Home Assistant types."""
173 
174  type_map = {
175  veraApi.VeraDimmer: Platform.LIGHT,
176  veraApi.VeraBinarySensor: Platform.BINARY_SENSOR,
177  veraApi.VeraSensor: Platform.SENSOR,
178  veraApi.VeraArmableDevice: Platform.SWITCH,
179  veraApi.VeraLock: Platform.LOCK,
180  veraApi.VeraThermostat: Platform.CLIMATE,
181  veraApi.VeraCurtain: Platform.COVER,
182  veraApi.VeraSceneController: Platform.SENSOR,
183  veraApi.VeraSwitch: Platform.SWITCH,
184  }
185 
186  def map_special_case(instance_class: type, entity_type: Platform) -> Platform:
187  if instance_class is veraApi.VeraSwitch and vera_device.device_id in remap:
188  return Platform.LIGHT
189  return entity_type
190 
191  return next(
192  iter(
193  map_special_case(instance_class, entity_type)
194  for instance_class, entity_type in type_map.items()
195  if isinstance(vera_device, instance_class)
196  ),
197  None,
198  )
None set_controller_data(HomeAssistant hass, ConfigEntry config_entry, ControllerData data)
Definition: common.py:47
set[Platform] get_configured_platforms(ControllerData controller_data)
Definition: common.py:28
ControllerData get_controller_data(HomeAssistant hass, ConfigEntry config_entry)
Definition: common.py:40
list[int] fix_device_id_list(list[Any] data)
Definition: config_flow.py:33
dict[str, list[int]] new_options(list[int] lights, list[int] exclude)
Definition: config_flow.py:48
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:75
bool async_setup(HomeAssistant hass, ConfigType base_config)
Definition: __init__.py:57
None _async_update_listener(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:164
Platform|None map_vera_device(veraApi.VeraDevice vera_device, list[int] remap)
Definition: __init__.py:171
bool async_unload_entry(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:150