Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support the Universal Devices ISY/IoX controllers."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from urllib.parse import urlparse
7 
8 from aiohttp import CookieJar
9 from pyisy import ISY, ISYConnectionError, ISYInvalidAuthError, ISYResponseParseError
10 from pyisy.constants import CONFIG_NETWORKING, CONFIG_PORTAL
11 import voluptuous as vol
12 
13 from homeassistant import config_entries
14 from homeassistant.const import (
15  CONF_HOST,
16  CONF_PASSWORD,
17  CONF_USERNAME,
18  CONF_VARIABLES,
19  EVENT_HOMEASSISTANT_STOP,
20  Platform,
21 )
22 from homeassistant.core import Event, HomeAssistant, callback
23 from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
24 from homeassistant.helpers import aiohttp_client, config_validation as cv
26 from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
27 
28 from .const import (
29  _LOGGER,
30  CONF_IGNORE_STRING,
31  CONF_NETWORK,
32  CONF_SENSOR_STRING,
33  CONF_TLS_VER,
34  DEFAULT_IGNORE_STRING,
35  DEFAULT_SENSOR_STRING,
36  DOMAIN,
37  ISY_CONF_FIRMWARE,
38  ISY_CONF_MODEL,
39  ISY_CONF_NAME,
40  MANUFACTURER,
41  PLATFORMS,
42  SCHEME_HTTP,
43  SCHEME_HTTPS,
44 )
45 from .helpers import _categorize_nodes, _categorize_programs
46 from .models import IsyData
47 from .services import async_setup_services, async_unload_services
48 from .util import _async_cleanup_registry_entries
49 
50 CONFIG_SCHEMA = vol.Schema(
51  cv.deprecated(DOMAIN),
52  extra=vol.ALLOW_EXTRA,
53 )
54 
55 
57  hass: HomeAssistant, entry: config_entries.ConfigEntry
58 ) -> bool:
59  """Set up the ISY 994 integration."""
60  hass.data.setdefault(DOMAIN, {})
61  isy_data = hass.data[DOMAIN][entry.entry_id] = IsyData()
62 
63  isy_config = entry.data
64  isy_options = entry.options
65 
66  # Required
67  user = isy_config[CONF_USERNAME]
68  password = isy_config[CONF_PASSWORD]
69  host = urlparse(isy_config[CONF_HOST])
70 
71  # Optional
72  tls_version = isy_config.get(CONF_TLS_VER)
73  ignore_identifier = isy_options.get(CONF_IGNORE_STRING, DEFAULT_IGNORE_STRING)
74  sensor_identifier = isy_options.get(CONF_SENSOR_STRING, DEFAULT_SENSOR_STRING)
75 
76  if host.scheme == SCHEME_HTTP:
77  https = False
78  port = host.port or 80
79  session = aiohttp_client.async_create_clientsession(
80  hass, verify_ssl=False, cookie_jar=CookieJar(unsafe=True)
81  )
82  elif host.scheme == SCHEME_HTTPS:
83  https = True
84  port = host.port or 443
85  session = aiohttp_client.async_get_clientsession(hass)
86  else:
87  _LOGGER.error("The ISY/IoX host value in configuration is invalid")
88  return False
89 
90  # Connect to ISY controller.
91  isy = ISY(
92  host.hostname,
93  port,
94  username=user,
95  password=password,
96  use_https=https,
97  tls_ver=tls_version,
98  webroot=host.path,
99  websession=session,
100  use_websocket=True,
101  )
102 
103  try:
104  async with asyncio.timeout(60):
105  await isy.initialize()
106  except TimeoutError as err:
107  raise ConfigEntryNotReady(
108  "Timed out initializing the ISY; device may be busy, trying again later:"
109  f" {err}"
110  ) from err
111  except ISYInvalidAuthError as err:
112  raise ConfigEntryAuthFailed(f"Invalid credentials for the ISY: {err}") from err
113  except ISYConnectionError as err:
114  raise ConfigEntryNotReady(
115  f"Failed to connect to the ISY, please adjust settings and try again: {err}"
116  ) from err
117  except ISYResponseParseError as err:
118  raise ConfigEntryNotReady(
119  "Invalid XML response from ISY; Ensure the ISY is running the latest"
120  f" firmware: {err}"
121  ) from err
122  except TypeError as err:
123  raise ConfigEntryNotReady(
124  f"Invalid response ISY, device is likely still starting: {err}"
125  ) from err
126 
127  _categorize_nodes(isy_data, isy.nodes, ignore_identifier, sensor_identifier)
128  _categorize_programs(isy_data, isy.programs)
129  # Gather ISY Variables to be added.
130  if isy.variables.children:
131  isy_data.devices[CONF_VARIABLES] = _create_service_device_info(
132  isy, name=CONF_VARIABLES.title(), unique_id=CONF_VARIABLES
133  )
134  numbers = isy_data.variables[Platform.NUMBER]
135  for vtype, _, vid in isy.variables.children:
136  numbers.append(isy.variables[vtype][vid])
137  if (
138  isy.conf[CONFIG_NETWORKING] or isy.conf[CONFIG_PORTAL]
139  ) and isy.networking.nobjs:
140  isy_data.devices[CONF_NETWORK] = _create_service_device_info(
141  isy, name=CONFIG_NETWORKING, unique_id=CONF_NETWORK
142  )
143  for resource in isy.networking.nobjs:
144  isy_data.net_resources.append(resource)
145 
146  # Dump ISY Clock Information. Future: Add ISY as sensor to Hass with attrs
147  _LOGGER.debug(repr(isy.clock))
148 
149  isy_data.root = isy
151 
152  # Load platforms for the devices in the ISY controller that we support.
153  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
154 
155  # Clean-up any old entities that we no longer provide.
156  _async_cleanup_registry_entries(hass, entry.entry_id)
157 
158  @callback
159  def _async_stop_auto_update(event: Event) -> None:
160  """Stop the isy auto update on Home Assistant Shutdown."""
161  _LOGGER.debug("ISY Stopping Event Stream and automatic updates")
162  isy.websocket.stop()
163 
164  _LOGGER.debug("ISY Starting Event Stream and automatic updates")
165  isy.websocket.start()
166 
167  entry.async_on_unload(entry.add_update_listener(_async_update_listener))
168  entry.async_on_unload(
169  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop_auto_update)
170  )
171 
172  # Register Integration-wide Services:
174 
175  return True
176 
177 
179  hass: HomeAssistant, entry: config_entries.ConfigEntry
180 ) -> None:
181  """Handle options update."""
182  await hass.config_entries.async_reload(entry.entry_id)
183 
184 
185 @callback
187  hass: HomeAssistant, entry: config_entries.ConfigEntry, isy: ISY
188 ) -> None:
189  device_registry = dr.async_get(hass)
190  device_registry.async_get_or_create(
191  config_entry_id=entry.entry_id,
192  connections={(dr.CONNECTION_NETWORK_MAC, isy.uuid)},
193  identifiers={(DOMAIN, isy.uuid)},
194  manufacturer=MANUFACTURER,
195  name=isy.conf[ISY_CONF_NAME],
196  model=isy.conf[ISY_CONF_MODEL],
197  sw_version=isy.conf[ISY_CONF_FIRMWARE],
198  configuration_url=isy.conn.url,
199  )
200 
201 
202 def _create_service_device_info(isy: ISY, name: str, unique_id: str) -> DeviceInfo:
203  """Create device info for ISY service devices."""
204  return DeviceInfo(
205  identifiers={
206  (
207  DOMAIN,
208  f"{isy.uuid}_{unique_id}",
209  )
210  },
211  manufacturer=MANUFACTURER,
212  name=f"{isy.conf[ISY_CONF_NAME]} {name}",
213  model=isy.conf[ISY_CONF_MODEL],
214  sw_version=isy.conf[ISY_CONF_FIRMWARE],
215  configuration_url=isy.conn.url,
216  via_device=(DOMAIN, isy.uuid),
217  entry_type=DeviceEntryType.SERVICE,
218  )
219 
220 
222  hass: HomeAssistant, entry: config_entries.ConfigEntry
223 ) -> bool:
224  """Unload a config entry."""
225  unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
226 
227  isy_data = hass.data[DOMAIN][entry.entry_id]
228 
229  isy: ISY = isy_data.root
230 
231  _LOGGER.debug("ISY Stopping Event Stream and automatic updates")
232  isy.websocket.stop()
233 
234  if unload_ok:
235  hass.data[DOMAIN].pop(entry.entry_id)
236 
238 
239  return unload_ok
240 
241 
243  hass: HomeAssistant,
244  config_entry: config_entries.ConfigEntry,
245  device_entry: dr.DeviceEntry,
246 ) -> bool:
247  """Remove ISY config entry from a device."""
248  isy_data = hass.data[DOMAIN][config_entry.entry_id]
249  return not device_entry.identifiers.intersection(
250  (DOMAIN, unique_id) for unique_id in isy_data.devices
251  )
None async_unload_services(HomeAssistant hass)
Definition: services.py:85
None async_setup_services(HomeAssistant hass)
Definition: __init__.py:72
None _categorize_programs(IsyData isy_data, Programs programs)
Definition: helpers.py:394
None _categorize_nodes(IsyData isy_data, Nodes nodes, str ignore_identifier, str sensor_identifier)
Definition: helpers.py:334
None _async_cleanup_registry_entries(HomeAssistant hass, str entry_id)
Definition: util.py:12
bool async_remove_config_entry_device(HomeAssistant hass, config_entries.ConfigEntry config_entry, dr.DeviceEntry device_entry)
Definition: __init__.py:246
bool async_unload_entry(HomeAssistant hass, config_entries.ConfigEntry entry)
Definition: __init__.py:223
DeviceInfo _create_service_device_info(ISY isy, str name, str unique_id)
Definition: __init__.py:202
None _async_get_or_create_isy_device_in_registry(HomeAssistant hass, config_entries.ConfigEntry entry, ISY isy)
Definition: __init__.py:188
None _async_update_listener(HomeAssistant hass, config_entries.ConfigEntry entry)
Definition: __init__.py:180
bool async_setup_entry(HomeAssistant hass, config_entries.ConfigEntry entry)
Definition: __init__.py:58