2020-07-19 20:48:08 +00:00
|
|
|
"""The Control4 integration."""
|
2021-07-19 12:14:09 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2020-07-19 20:48:08 +00:00
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
|
|
|
|
from aiohttp import client_exceptions
|
|
|
|
from pyControl4.account import C4Account
|
|
|
|
from pyControl4.director import C4Director
|
|
|
|
from pyControl4.error_handling import BadCredentials
|
|
|
|
|
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
|
|
from homeassistant.const import (
|
|
|
|
CONF_HOST,
|
|
|
|
CONF_PASSWORD,
|
|
|
|
CONF_SCAN_INTERVAL,
|
|
|
|
CONF_TOKEN,
|
|
|
|
CONF_USERNAME,
|
2021-12-03 16:51:30 +00:00
|
|
|
Platform,
|
2020-07-19 20:48:08 +00:00
|
|
|
)
|
|
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from homeassistant.exceptions import ConfigEntryNotReady
|
2020-08-30 18:27:46 +00:00
|
|
|
from homeassistant.helpers import aiohttp_client, device_registry as dr
|
2021-10-25 10:23:11 +00:00
|
|
|
from homeassistant.helpers.entity import DeviceInfo
|
2020-08-30 18:27:46 +00:00
|
|
|
from homeassistant.helpers.update_coordinator import (
|
|
|
|
CoordinatorEntity,
|
|
|
|
DataUpdateCoordinator,
|
|
|
|
)
|
2020-07-19 20:48:08 +00:00
|
|
|
|
|
|
|
from .const import (
|
|
|
|
CONF_ACCOUNT,
|
|
|
|
CONF_CONFIG_LISTENER,
|
|
|
|
CONF_CONTROLLER_UNIQUE_ID,
|
|
|
|
CONF_DIRECTOR,
|
|
|
|
CONF_DIRECTOR_ALL_ITEMS,
|
|
|
|
CONF_DIRECTOR_MODEL,
|
|
|
|
CONF_DIRECTOR_SW_VERSION,
|
|
|
|
CONF_DIRECTOR_TOKEN_EXPIRATION,
|
|
|
|
DEFAULT_SCAN_INTERVAL,
|
|
|
|
DOMAIN,
|
|
|
|
)
|
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2021-12-03 16:51:30 +00:00
|
|
|
PLATFORMS = [Platform.LIGHT]
|
2020-07-19 20:48:08 +00:00
|
|
|
|
|
|
|
|
2021-05-27 15:39:06 +00:00
|
|
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
2020-07-19 20:48:08 +00:00
|
|
|
"""Set up Control4 from a config entry."""
|
2021-03-29 23:23:44 +00:00
|
|
|
hass.data.setdefault(DOMAIN, {})
|
2020-07-19 20:48:08 +00:00
|
|
|
entry_data = hass.data[DOMAIN].setdefault(entry.entry_id, {})
|
|
|
|
account_session = aiohttp_client.async_get_clientsession(hass)
|
|
|
|
|
|
|
|
config = entry.data
|
|
|
|
account = C4Account(config[CONF_USERNAME], config[CONF_PASSWORD], account_session)
|
|
|
|
try:
|
|
|
|
await account.getAccountBearerToken()
|
|
|
|
except client_exceptions.ClientError as exception:
|
|
|
|
_LOGGER.error("Error connecting to Control4 account API: %s", exception)
|
2020-08-28 11:50:32 +00:00
|
|
|
raise ConfigEntryNotReady from exception
|
2020-07-19 20:48:08 +00:00
|
|
|
except BadCredentials as exception:
|
|
|
|
_LOGGER.error(
|
|
|
|
"Error authenticating with Control4 account API, incorrect username or password: %s",
|
|
|
|
exception,
|
|
|
|
)
|
|
|
|
return False
|
|
|
|
entry_data[CONF_ACCOUNT] = account
|
|
|
|
|
|
|
|
controller_unique_id = config[CONF_CONTROLLER_UNIQUE_ID]
|
|
|
|
entry_data[CONF_CONTROLLER_UNIQUE_ID] = controller_unique_id
|
|
|
|
|
|
|
|
director_token_dict = await account.getDirectorBearerToken(controller_unique_id)
|
|
|
|
director_session = aiohttp_client.async_get_clientsession(hass, verify_ssl=False)
|
|
|
|
|
|
|
|
director = C4Director(
|
|
|
|
config[CONF_HOST], director_token_dict[CONF_TOKEN], director_session
|
|
|
|
)
|
|
|
|
entry_data[CONF_DIRECTOR] = director
|
|
|
|
entry_data[CONF_DIRECTOR_TOKEN_EXPIRATION] = director_token_dict["token_expiration"]
|
|
|
|
|
|
|
|
# Add Control4 controller to device registry
|
|
|
|
controller_href = (await account.getAccountControllers())["href"]
|
|
|
|
entry_data[CONF_DIRECTOR_SW_VERSION] = await account.getControllerOSVersion(
|
|
|
|
controller_href
|
|
|
|
)
|
|
|
|
|
|
|
|
_, model, mac_address = controller_unique_id.split("_", 3)
|
|
|
|
entry_data[CONF_DIRECTOR_MODEL] = model.upper()
|
|
|
|
|
2021-10-27 22:58:14 +00:00
|
|
|
device_registry = dr.async_get(hass)
|
2020-07-19 20:48:08 +00:00
|
|
|
device_registry.async_get_or_create(
|
|
|
|
config_entry_id=entry.entry_id,
|
|
|
|
identifiers={(DOMAIN, controller_unique_id)},
|
|
|
|
connections={(dr.CONNECTION_NETWORK_MAC, mac_address)},
|
|
|
|
manufacturer="Control4",
|
|
|
|
name=controller_unique_id,
|
|
|
|
model=entry_data[CONF_DIRECTOR_MODEL],
|
|
|
|
sw_version=entry_data[CONF_DIRECTOR_SW_VERSION],
|
|
|
|
)
|
|
|
|
|
|
|
|
# Store all items found on controller for platforms to use
|
|
|
|
director_all_items = await director.getAllItemInfo()
|
|
|
|
director_all_items = json.loads(director_all_items)
|
|
|
|
entry_data[CONF_DIRECTOR_ALL_ITEMS] = director_all_items
|
|
|
|
|
|
|
|
# Load options from config entry
|
|
|
|
entry_data[CONF_SCAN_INTERVAL] = entry.options.get(
|
|
|
|
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
|
|
|
)
|
|
|
|
|
|
|
|
entry_data[CONF_CONFIG_LISTENER] = entry.add_update_listener(update_listener)
|
|
|
|
|
2021-04-26 17:46:55 +00:00
|
|
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
2020-07-19 20:48:08 +00:00
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
async def update_listener(hass, config_entry):
|
|
|
|
"""Update when config_entry options update."""
|
|
|
|
_LOGGER.debug("Config entry was updated, rerunning setup")
|
|
|
|
await hass.config_entries.async_reload(config_entry.entry_id)
|
|
|
|
|
|
|
|
|
2021-10-06 08:48:11 +00:00
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
2020-07-19 20:48:08 +00:00
|
|
|
"""Unload a config entry."""
|
2021-04-26 17:46:55 +00:00
|
|
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
|
|
|
|
2020-07-19 20:48:08 +00:00
|
|
|
hass.data[DOMAIN][entry.entry_id][CONF_CONFIG_LISTENER]()
|
|
|
|
if unload_ok:
|
|
|
|
hass.data[DOMAIN].pop(entry.entry_id)
|
|
|
|
_LOGGER.debug("Unloaded entry for %s", entry.entry_id)
|
|
|
|
|
|
|
|
return unload_ok
|
|
|
|
|
|
|
|
|
|
|
|
async def get_items_of_category(hass: HomeAssistant, entry: ConfigEntry, category: str):
|
|
|
|
"""Return a list of all Control4 items with the specified category."""
|
|
|
|
director_all_items = hass.data[DOMAIN][entry.entry_id][CONF_DIRECTOR_ALL_ITEMS]
|
|
|
|
return_list = []
|
|
|
|
for item in director_all_items:
|
|
|
|
if "categories" in item and category in item["categories"]:
|
|
|
|
return_list.append(item)
|
|
|
|
return return_list
|
|
|
|
|
|
|
|
|
2020-08-30 18:27:46 +00:00
|
|
|
class Control4Entity(CoordinatorEntity):
|
2020-07-19 20:48:08 +00:00
|
|
|
"""Base entity for Control4."""
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
entry_data: dict,
|
|
|
|
coordinator: DataUpdateCoordinator,
|
|
|
|
name: str,
|
|
|
|
idx: int,
|
2021-07-19 12:14:09 +00:00
|
|
|
device_name: str | None,
|
|
|
|
device_manufacturer: str | None,
|
|
|
|
device_model: str | None,
|
2020-07-19 20:48:08 +00:00
|
|
|
device_id: int,
|
2021-05-20 15:51:39 +00:00
|
|
|
) -> None:
|
2020-07-19 20:48:08 +00:00
|
|
|
"""Initialize a Control4 entity."""
|
2020-08-30 18:27:46 +00:00
|
|
|
super().__init__(coordinator)
|
2020-08-04 19:35:28 +00:00
|
|
|
self.entry_data = entry_data
|
2021-10-25 10:23:11 +00:00
|
|
|
self._attr_name = name
|
|
|
|
self._attr_unique_id = str(idx)
|
2020-07-19 20:48:08 +00:00
|
|
|
self._idx = idx
|
|
|
|
self._controller_unique_id = entry_data[CONF_CONTROLLER_UNIQUE_ID]
|
|
|
|
self._device_name = device_name
|
|
|
|
self._device_manufacturer = device_manufacturer
|
|
|
|
self._device_model = device_model
|
|
|
|
self._device_id = device_id
|
|
|
|
|
|
|
|
@property
|
2021-10-25 10:23:11 +00:00
|
|
|
def device_info(self) -> DeviceInfo:
|
2020-07-19 20:48:08 +00:00
|
|
|
"""Return info of parent Control4 device of entity."""
|
2021-10-25 10:23:11 +00:00
|
|
|
return DeviceInfo(
|
|
|
|
identifiers={(DOMAIN, str(self._device_id))},
|
|
|
|
manufacturer=self._device_manufacturer,
|
|
|
|
model=self._device_model,
|
|
|
|
name=self._device_name,
|
|
|
|
via_device=(DOMAIN, self._controller_unique_id),
|
|
|
|
)
|