Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The Control4 integration."""
2 
3 from __future__ import annotations
4 
5 import json
6 import logging
7 
8 from aiohttp import client_exceptions
9 from pyControl4.account import C4Account
10 from pyControl4.director import C4Director
11 from pyControl4.error_handling import BadCredentials
12 
13 from homeassistant.config_entries import ConfigEntry
14 from homeassistant.const import (
15  CONF_HOST,
16  CONF_PASSWORD,
17  CONF_SCAN_INTERVAL,
18  CONF_TOKEN,
19  CONF_USERNAME,
20  Platform,
21 )
22 from homeassistant.core import HomeAssistant
23 from homeassistant.exceptions import ConfigEntryNotReady
24 from homeassistant.helpers import aiohttp_client, device_registry as dr
25 
26 from .const import (
27  API_RETRY_TIMES,
28  CONF_ACCOUNT,
29  CONF_CONFIG_LISTENER,
30  CONF_CONTROLLER_UNIQUE_ID,
31  CONF_DIRECTOR,
32  CONF_DIRECTOR_ALL_ITEMS,
33  CONF_DIRECTOR_MODEL,
34  CONF_DIRECTOR_SW_VERSION,
35  CONF_UI_CONFIGURATION,
36  DEFAULT_SCAN_INTERVAL,
37  DOMAIN,
38 )
39 
40 _LOGGER = logging.getLogger(__name__)
41 
42 PLATFORMS = [Platform.LIGHT, Platform.MEDIA_PLAYER]
43 
44 
45 async def call_c4_api_retry(func, *func_args):
46  """Call C4 API function and retry on failure."""
47  # Ruff doesn't understand this loop - the exception is always raised after the retries
48  for i in range(API_RETRY_TIMES): # noqa: RET503
49  try:
50  return await func(*func_args)
51  except client_exceptions.ClientError as exception:
52  _LOGGER.error("Error connecting to Control4 account API: %s", exception)
53  if i == API_RETRY_TIMES - 1:
54  raise ConfigEntryNotReady(exception) from exception
55 
56 
57 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
58  """Set up Control4 from a config entry."""
59  hass.data.setdefault(DOMAIN, {})
60  entry_data = hass.data[DOMAIN].setdefault(entry.entry_id, {})
61  account_session = aiohttp_client.async_get_clientsession(hass)
62 
63  config = entry.data
64  account = C4Account(config[CONF_USERNAME], config[CONF_PASSWORD], account_session)
65  try:
66  await account.getAccountBearerToken()
67  except client_exceptions.ClientError as exception:
68  _LOGGER.error("Error connecting to Control4 account API: %s", exception)
69  raise ConfigEntryNotReady from exception
70  except BadCredentials as exception:
71  _LOGGER.error(
72  (
73  "Error authenticating with Control4 account API, incorrect username or"
74  " password: %s"
75  ),
76  exception,
77  )
78  return False
79  entry_data[CONF_ACCOUNT] = account
80 
81  controller_unique_id = config[CONF_CONTROLLER_UNIQUE_ID]
82  entry_data[CONF_CONTROLLER_UNIQUE_ID] = controller_unique_id
83 
84  director_token_dict = await call_c4_api_retry(
85  account.getDirectorBearerToken, controller_unique_id
86  )
87 
88  director_session = aiohttp_client.async_get_clientsession(hass, verify_ssl=False)
89  director = C4Director(
90  config[CONF_HOST], director_token_dict[CONF_TOKEN], director_session
91  )
92  entry_data[CONF_DIRECTOR] = director
93 
94  controller_href = (await call_c4_api_retry(account.getAccountControllers))["href"]
95  entry_data[CONF_DIRECTOR_SW_VERSION] = await call_c4_api_retry(
96  account.getControllerOSVersion, controller_href
97  )
98 
99  _, model, mac_address = controller_unique_id.split("_", 3)
100  entry_data[CONF_DIRECTOR_MODEL] = model.upper()
101 
102  device_registry = dr.async_get(hass)
103  device_registry.async_get_or_create(
104  config_entry_id=entry.entry_id,
105  identifiers={(DOMAIN, controller_unique_id)},
106  connections={(dr.CONNECTION_NETWORK_MAC, mac_address)},
107  manufacturer="Control4",
108  name=controller_unique_id,
109  model=entry_data[CONF_DIRECTOR_MODEL],
110  sw_version=entry_data[CONF_DIRECTOR_SW_VERSION],
111  )
112 
113  # Store all items found on controller for platforms to use
114  director_all_items = await director.getAllItemInfo()
115  director_all_items = json.loads(director_all_items)
116  entry_data[CONF_DIRECTOR_ALL_ITEMS] = director_all_items
117 
118  # Check if OS version is 3 or higher to get UI configuration
119  entry_data[CONF_UI_CONFIGURATION] = None
120  if int(entry_data[CONF_DIRECTOR_SW_VERSION].split(".")[0]) >= 3:
121  entry_data[CONF_UI_CONFIGURATION] = json.loads(
122  await director.getUiConfiguration()
123  )
124 
125  # Load options from config entry
126  entry_data[CONF_SCAN_INTERVAL] = entry.options.get(
127  CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
128  )
129 
130  entry_data[CONF_CONFIG_LISTENER] = entry.add_update_listener(update_listener)
131 
132  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
133 
134  return True
135 
136 
137 async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
138  """Update when config_entry options update."""
139  _LOGGER.debug("Config entry was updated, rerunning setup")
140  await hass.config_entries.async_reload(config_entry.entry_id)
141 
142 
143 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
144  """Unload a config entry."""
145  unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
146 
147  hass.data[DOMAIN][entry.entry_id][CONF_CONFIG_LISTENER]()
148  if unload_ok:
149  hass.data[DOMAIN].pop(entry.entry_id)
150  _LOGGER.debug("Unloaded entry for %s", entry.entry_id)
151 
152  return unload_ok
153 
154 
155 async def get_items_of_category(hass: HomeAssistant, entry: ConfigEntry, category: str):
156  """Return a list of all Control4 items with the specified category."""
157  director_all_items = hass.data[DOMAIN][entry.entry_id][CONF_DIRECTOR_ALL_ITEMS]
158  return [
159  item
160  for item in director_all_items
161  if "categories" in item and category in item["categories"]
162  ]
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:143
def call_c4_api_retry(func, *func_args)
Definition: __init__.py:45
None update_listener(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:137
def get_items_of_category(HomeAssistant hass, ConfigEntry entry, str category)
Definition: __init__.py:155
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:57