Initial Home Assistant commit
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user