216 lines
7.2 KiB
Python
216 lines
7.2 KiB
Python
"""The Wyze Home Assistant Integration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from aiohttp.client_exceptions import ClientConnectorError
|
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryNotReady, SOURCE_IMPORT
|
|
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
|
from homeassistant.helpers import device_registry as dr
|
|
from homeassistant.helpers.check_config import HomeAssistantConfig
|
|
from homeassistant.components import bluetooth
|
|
from wyzeapy import Wyzeapy
|
|
from wyzeapy.exceptions import AccessTokenError
|
|
from wyzeapy.wyze_auth_lib import Token
|
|
|
|
from .const import (
|
|
DOMAIN,
|
|
CONF_CLIENT,
|
|
ACCESS_TOKEN,
|
|
REFRESH_TOKEN,
|
|
REFRESH_TIME,
|
|
WYZE_NOTIFICATION_TOGGLE,
|
|
BULB_LOCAL_CONTROL,
|
|
DEFAULT_LOCAL_CONTROL,
|
|
KEY_ID,
|
|
API_KEY,
|
|
)
|
|
from .coordinator import WyzeLockBoltCoordinator
|
|
from .token_manager import TokenManager
|
|
|
|
PLATFORMS = [
|
|
"light",
|
|
"switch",
|
|
"lock",
|
|
"climate",
|
|
"alarm_control_panel",
|
|
"sensor",
|
|
"siren",
|
|
"cover",
|
|
"number",
|
|
"button",
|
|
"camera",
|
|
] # Fixme: Re add scene
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
# noinspection PyUnusedLocal
|
|
async def async_setup(
|
|
hass: HomeAssistant, config: HomeAssistantConfig, discovery_info=None
|
|
):
|
|
# pylint: disable=unused-argument
|
|
"""Set up the WyzeApi domain."""
|
|
if hass.config_entries.async_entries(DOMAIN):
|
|
_LOGGER.debug(
|
|
"Nothing to import from configuration.yaml, loading from Integrations",
|
|
)
|
|
return True
|
|
|
|
# noinspection SpellCheckingInspection
|
|
domainconfig = config.get(DOMAIN)
|
|
# pylint: disable=logging-not-lazy
|
|
_LOGGER.debug(
|
|
"Importing config information for %s from configuration.yml"
|
|
% domainconfig[CONF_USERNAME]
|
|
)
|
|
if hass.config_entries.async_entries(DOMAIN):
|
|
_LOGGER.debug("Found existing config entries")
|
|
for entry in hass.config_entries.async_entries(DOMAIN):
|
|
if entry:
|
|
entry_data = entry.as_dict().get("data")
|
|
hass.config_entries.async_update_entry(
|
|
entry,
|
|
data=entry_data,
|
|
)
|
|
break
|
|
else:
|
|
_LOGGER.debug("Creating new config entry")
|
|
hass.async_create_task(
|
|
hass.config_entries.flow.async_init(
|
|
DOMAIN,
|
|
context={"source": SOURCE_IMPORT},
|
|
data={
|
|
CONF_USERNAME: domainconfig[CONF_USERNAME],
|
|
CONF_PASSWORD: domainconfig[CONF_PASSWORD],
|
|
ACCESS_TOKEN: domainconfig[ACCESS_TOKEN],
|
|
REFRESH_TOKEN: domainconfig[REFRESH_TOKEN],
|
|
REFRESH_TIME: domainconfig[REFRESH_TIME],
|
|
KEY_ID: domainconfig[KEY_ID],
|
|
API_KEY: domainconfig[API_KEY],
|
|
},
|
|
)
|
|
)
|
|
return True
|
|
|
|
|
|
# noinspection DuplicatedCode
|
|
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
|
"""Set up Wyze Home Assistant Integration from a config entry."""
|
|
|
|
hass.data.setdefault(DOMAIN, {})
|
|
|
|
key_id = config_entry.data.get(KEY_ID)
|
|
api_key = config_entry.data.get(API_KEY)
|
|
|
|
client = await Wyzeapy.create()
|
|
token = None
|
|
if config_entry.data.get(ACCESS_TOKEN):
|
|
token = Token(
|
|
config_entry.data.get(ACCESS_TOKEN),
|
|
config_entry.data.get(REFRESH_TOKEN),
|
|
float(config_entry.data.get(REFRESH_TIME)),
|
|
)
|
|
a_tkn_manager = TokenManager(hass, config_entry)
|
|
client.register_for_token_callback(a_tkn_manager.token_callback)
|
|
# We should probably try/catch here to invalidate the login credentials and throw a notification if we cannot get
|
|
# a login with the token
|
|
try:
|
|
await client.login(
|
|
config_entry.data.get(CONF_USERNAME),
|
|
config_entry.data.get(CONF_PASSWORD),
|
|
key_id,
|
|
api_key,
|
|
token,
|
|
)
|
|
except ClientConnectorError as e:
|
|
raise ConfigEntryNotReady("Unable to login due to network issues.") from e
|
|
except AccessTokenError as e:
|
|
_LOGGER.error(
|
|
"Wyzeapi: Could not login. Please re-login through integration configuration"
|
|
)
|
|
_LOGGER.error(e)
|
|
raise ConfigEntryAuthFailed("Unable to login, please re-login.") from None
|
|
|
|
hass.data[DOMAIN][config_entry.entry_id] = {
|
|
CONF_CLIENT: client,
|
|
"key_id": KEY_ID,
|
|
"api_key": API_KEY,
|
|
"coordinators": {},
|
|
}
|
|
await setup_coordinators(hass, config_entry, client)
|
|
|
|
options_dict = {
|
|
BULB_LOCAL_CONTROL: config_entry.options.get(
|
|
BULB_LOCAL_CONTROL, DEFAULT_LOCAL_CONTROL
|
|
)
|
|
}
|
|
hass.config_entries.async_update_entry(config_entry, options=options_dict)
|
|
|
|
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
|
|
|
mac_addresses = await client.unique_device_ids
|
|
|
|
mac_addresses.add(WYZE_NOTIFICATION_TOGGLE)
|
|
|
|
hms_service = await client.hms_service
|
|
hms_id = hms_service.hms_id
|
|
if hms_id is not None:
|
|
mac_addresses.add(hms_id)
|
|
|
|
device_registry = dr.async_get(hass)
|
|
for device in dr.async_entries_for_config_entry(
|
|
device_registry, config_entry.entry_id
|
|
):
|
|
for identifier in device.identifiers:
|
|
# domain has to remain here. If it is removed the integration will remove all entities for not being in
|
|
# the mac address list each boot.
|
|
domain, mac = identifier
|
|
if mac not in mac_addresses:
|
|
_LOGGER.warning(
|
|
"%s is not in the mac_addresses list, removing the entry", mac
|
|
)
|
|
device_registry.async_remove_device(device.id)
|
|
return True
|
|
|
|
|
|
async def options_update_listener(hass: HomeAssistant, config_entry: ConfigEntry):
|
|
"""Handle options update."""
|
|
_LOGGER.debug("Updated options")
|
|
entry_data = config_entry.as_dict().get("data")
|
|
hass.config_entries.async_update_entry(
|
|
config_entry,
|
|
data=entry_data,
|
|
)
|
|
_LOGGER.debug("Reload entry: " + config_entry.entry_id)
|
|
await hass.config_entries.async_reload(config_entry.entry_id)
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Unload a config entry."""
|
|
|
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
|
|
|
|
|
async def setup_coordinators(
|
|
hass: HomeAssistant, config_entry: ConfigEntry, client: Wyzeapy
|
|
):
|
|
"""Set up coordinators for Wyze devices that require Bluetooth."""
|
|
# Check if Bluetooth is active and functioning
|
|
if bluetooth.async_scanner_count(hass, connectable=True) == 0:
|
|
_LOGGER.info(
|
|
"Bluetooth is not active or no scanners available. Skipping WyzeLockBoltCoordinator setup."
|
|
)
|
|
return
|
|
|
|
lock_service = await client.lock_service
|
|
for lock in await lock_service.get_locks():
|
|
if lock.product_model == "YD_BT1":
|
|
coordinators = hass.data[DOMAIN][config_entry.entry_id].setdefault(
|
|
"coordinators", {}
|
|
)
|
|
coordinators[lock.mac] = WyzeLockBoltCoordinator(hass, lock_service, lock)
|
|
await coordinators[lock.mac].update_lock_info()
|