2019-02-13 20:21:14 +00:00
|
|
|
"""Support for Tuya Smart devices."""
|
2021-09-30 10:02:56 +00:00
|
|
|
|
|
|
|
import itertools
|
2018-07-12 08:19:35 +00:00
|
|
|
import logging
|
2019-12-05 05:11:13 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
from tuya_iot import (
|
2021-10-05 17:21:55 +00:00
|
|
|
AuthType,
|
2021-09-30 10:02:56 +00:00
|
|
|
TuyaDevice,
|
|
|
|
TuyaDeviceListener,
|
|
|
|
TuyaDeviceManager,
|
|
|
|
TuyaHomeManager,
|
|
|
|
TuyaOpenAPI,
|
|
|
|
TuyaOpenMQ,
|
2020-10-28 01:26:18 +00:00
|
|
|
)
|
2018-07-12 08:19:35 +00:00
|
|
|
|
2021-05-09 18:26:26 +00:00
|
|
|
from homeassistant.config_entries import ConfigEntry
|
2020-10-28 01:26:18 +00:00
|
|
|
from homeassistant.core import HomeAssistant, callback
|
2021-09-30 10:02:56 +00:00
|
|
|
from homeassistant.helpers import device_registry
|
|
|
|
from homeassistant.helpers.dispatcher import dispatcher_send
|
2020-05-11 03:01:00 +00:00
|
|
|
|
|
|
|
from .const import (
|
2021-09-30 10:02:56 +00:00
|
|
|
CONF_ACCESS_ID,
|
|
|
|
CONF_ACCESS_SECRET,
|
|
|
|
CONF_APP_TYPE,
|
2021-10-05 17:21:55 +00:00
|
|
|
CONF_AUTH_TYPE,
|
2021-09-30 10:02:56 +00:00
|
|
|
CONF_COUNTRY_CODE,
|
|
|
|
CONF_ENDPOINT,
|
|
|
|
CONF_PASSWORD,
|
|
|
|
CONF_PROJECT_TYPE,
|
|
|
|
CONF_USERNAME,
|
2020-05-11 03:01:00 +00:00
|
|
|
DOMAIN,
|
2021-09-30 10:02:56 +00:00
|
|
|
PLATFORMS,
|
|
|
|
TUYA_DEVICE_MANAGER,
|
2020-05-11 03:01:00 +00:00
|
|
|
TUYA_DISCOVERY_NEW,
|
2021-09-30 10:02:56 +00:00
|
|
|
TUYA_HA_DEVICES,
|
|
|
|
TUYA_HA_SIGNAL_UPDATE_ENTITY,
|
|
|
|
TUYA_HA_TUYA_MAP,
|
|
|
|
TUYA_HOME_MANAGER,
|
|
|
|
TUYA_MQTT_LISTENER,
|
2020-05-11 03:01:00 +00:00
|
|
|
)
|
2018-07-12 08:19:35 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
|
|
"""Async setup hass config entry."""
|
2021-10-05 20:32:48 +00:00
|
|
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
|
|
|
|
TUYA_HA_TUYA_MAP: {},
|
|
|
|
TUYA_HA_DEVICES: set(),
|
|
|
|
}
|
2020-05-11 03:01:00 +00:00
|
|
|
|
2021-10-05 17:21:55 +00:00
|
|
|
# Project type has been renamed to auth type in the upstream Tuya IoT SDK.
|
|
|
|
# This migrates existing config entries to reflect that name change.
|
|
|
|
if CONF_PROJECT_TYPE in entry.data:
|
|
|
|
data = {**entry.data, CONF_AUTH_TYPE: entry.data[CONF_PROJECT_TYPE]}
|
|
|
|
data.pop(CONF_PROJECT_TYPE)
|
|
|
|
hass.config_entries.async_update_entry(entry, data=data)
|
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
success = await _init_tuya_sdk(hass, entry)
|
2021-10-05 20:32:48 +00:00
|
|
|
|
|
|
|
if not success:
|
|
|
|
hass.data[DOMAIN].pop(entry.entry_id)
|
|
|
|
|
|
|
|
if not hass.data[DOMAIN]:
|
|
|
|
hass.data.pop(DOMAIN)
|
|
|
|
|
2021-10-05 10:59:51 +00:00
|
|
|
return bool(success)
|
2018-07-12 08:19:35 +00:00
|
|
|
|
2020-10-28 01:26:18 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
async def _init_tuya_sdk(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
2021-10-05 17:21:55 +00:00
|
|
|
auth_type = AuthType(entry.data[CONF_AUTH_TYPE])
|
2021-09-30 10:02:56 +00:00
|
|
|
api = TuyaOpenAPI(
|
2021-10-05 17:21:55 +00:00
|
|
|
endpoint=entry.data[CONF_ENDPOINT],
|
|
|
|
access_id=entry.data[CONF_ACCESS_ID],
|
|
|
|
access_secret=entry.data[CONF_ACCESS_SECRET],
|
|
|
|
auth_type=auth_type,
|
2021-09-30 10:02:56 +00:00
|
|
|
)
|
2020-10-28 01:26:18 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
api.set_dev_channel("hass")
|
2020-10-28 01:26:18 +00:00
|
|
|
|
2021-10-05 17:21:55 +00:00
|
|
|
if auth_type == AuthType.CUSTOM:
|
2021-09-30 10:02:56 +00:00
|
|
|
response = await hass.async_add_executor_job(
|
2021-10-05 17:21:55 +00:00
|
|
|
api.connect, entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD]
|
2021-09-30 10:02:56 +00:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
response = await hass.async_add_executor_job(
|
2021-10-05 17:21:55 +00:00
|
|
|
api.connect,
|
2021-09-30 10:02:56 +00:00
|
|
|
entry.data[CONF_USERNAME],
|
|
|
|
entry.data[CONF_PASSWORD],
|
|
|
|
entry.data[CONF_COUNTRY_CODE],
|
|
|
|
entry.data[CONF_APP_TYPE],
|
|
|
|
)
|
2020-10-28 01:26:18 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
if response.get("success", False) is False:
|
|
|
|
_LOGGER.error("Tuya login error response: %s", response)
|
|
|
|
return False
|
2020-10-28 01:26:18 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
tuya_mq = TuyaOpenMQ(api)
|
|
|
|
tuya_mq.start()
|
2020-10-28 01:26:18 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
device_manager = TuyaDeviceManager(api, tuya_mq)
|
2020-04-29 11:46:27 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
# Get device list
|
|
|
|
home_manager = TuyaHomeManager(api, tuya_mq, device_manager)
|
|
|
|
await hass.async_add_executor_job(home_manager.update_device_cache)
|
|
|
|
hass.data[DOMAIN][entry.entry_id][TUYA_HOME_MANAGER] = home_manager
|
2020-04-29 11:46:27 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
listener = DeviceListener(hass, entry)
|
|
|
|
hass.data[DOMAIN][entry.entry_id][TUYA_MQTT_LISTENER] = listener
|
|
|
|
device_manager.add_device_listener(listener)
|
|
|
|
hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER] = device_manager
|
2020-04-29 11:46:27 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
# Clean up device entities
|
|
|
|
await cleanup_device_registry(hass, entry)
|
2020-10-28 01:26:18 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
_LOGGER.debug("init support type->%s", PLATFORMS)
|
2020-10-28 01:26:18 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
2018-07-12 08:19:35 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
return True
|
2021-04-17 04:01:24 +00:00
|
|
|
|
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
async def cleanup_device_registry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
|
|
"""Remove deleted device registry entry if there are no remaining entities."""
|
2018-07-12 08:19:35 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
device_registry_object = device_registry.async_get(hass)
|
|
|
|
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
|
2018-07-12 08:19:35 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
for dev_id, device_entry in list(device_registry_object.devices.items()):
|
|
|
|
for item in device_entry.identifiers:
|
|
|
|
if DOMAIN == item[0] and item[1] not in device_manager.device_map:
|
|
|
|
device_registry_object.async_remove_device(dev_id)
|
|
|
|
break
|
2018-07-12 08:19:35 +00:00
|
|
|
|
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
@callback
|
2021-10-12 03:32:15 +00:00
|
|
|
def async_remove_hass_device(
|
|
|
|
hass: HomeAssistant, entry: ConfigEntry, device_id: str
|
|
|
|
) -> None:
|
2021-09-30 10:02:56 +00:00
|
|
|
"""Remove device from hass cache."""
|
|
|
|
device_registry_object = device_registry.async_get(hass)
|
2021-10-12 03:32:15 +00:00
|
|
|
device_entry = device_registry_object.async_get_device(
|
|
|
|
identifiers={(DOMAIN, device_id)}
|
|
|
|
)
|
|
|
|
if device_entry is not None:
|
|
|
|
device_registry_object.async_remove_device(device_entry.id)
|
|
|
|
hass.data[DOMAIN][entry.entry_id][TUYA_HA_DEVICES].discard(device_id)
|
2018-07-12 08:19:35 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
2020-05-11 03:01:00 +00:00
|
|
|
"""Unloading the Tuya platforms."""
|
2021-09-30 10:02:56 +00:00
|
|
|
_LOGGER.debug("integration unload")
|
|
|
|
unload = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
|
|
|
if unload:
|
|
|
|
device_manager = hass.data[DOMAIN][entry.entry_id][TUYA_DEVICE_MANAGER]
|
|
|
|
device_manager.mq.stop()
|
|
|
|
device_manager.remove_device_listener(
|
|
|
|
hass.data[DOMAIN][entry.entry_id][TUYA_MQTT_LISTENER]
|
|
|
|
)
|
|
|
|
|
2021-10-05 20:32:48 +00:00
|
|
|
hass.data[DOMAIN].pop(entry.entry_id)
|
|
|
|
|
|
|
|
if not hass.data[DOMAIN]:
|
|
|
|
hass.data.pop(DOMAIN)
|
2020-05-11 03:01:00 +00:00
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
return unload
|
2020-05-11 03:01:00 +00:00
|
|
|
|
|
|
|
|
2021-09-30 10:02:56 +00:00
|
|
|
class DeviceListener(TuyaDeviceListener):
|
|
|
|
"""Device Update Listener."""
|
|
|
|
|
|
|
|
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
|
|
"""Init DeviceListener."""
|
|
|
|
|
|
|
|
self.hass = hass
|
|
|
|
self.entry = entry
|
|
|
|
|
|
|
|
def update_device(self, device: TuyaDevice) -> None:
|
|
|
|
"""Update device status."""
|
|
|
|
if device.id in self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_DEVICES]:
|
2020-10-28 01:26:18 +00:00
|
|
|
_LOGGER.debug(
|
2021-09-30 10:02:56 +00:00
|
|
|
"_update-->%s;->>%s",
|
|
|
|
self,
|
|
|
|
device.id,
|
2020-05-11 03:01:00 +00:00
|
|
|
)
|
2021-09-30 10:02:56 +00:00
|
|
|
dispatcher_send(self.hass, f"{TUYA_HA_SIGNAL_UPDATE_ENTITY}_{device.id}")
|
|
|
|
|
|
|
|
def add_device(self, device: TuyaDevice) -> None:
|
|
|
|
"""Add device added listener."""
|
|
|
|
device_add = False
|
|
|
|
|
|
|
|
if device.category in itertools.chain(
|
|
|
|
*self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_TUYA_MAP].values()
|
|
|
|
):
|
|
|
|
ha_tuya_map = self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_TUYA_MAP]
|
2021-10-12 03:32:15 +00:00
|
|
|
self.hass.add_job(
|
|
|
|
async_remove_hass_device, self.hass, self.entry, device.id
|
|
|
|
)
|
2021-09-30 10:02:56 +00:00
|
|
|
|
|
|
|
for domain, tuya_list in ha_tuya_map.items():
|
|
|
|
if device.category in tuya_list:
|
|
|
|
device_add = True
|
|
|
|
_LOGGER.debug(
|
|
|
|
"Add device category->%s; domain-> %s",
|
|
|
|
device.category,
|
|
|
|
domain,
|
|
|
|
)
|
|
|
|
self.hass.data[DOMAIN][self.entry.entry_id][TUYA_HA_DEVICES].add(
|
|
|
|
device.id
|
|
|
|
)
|
|
|
|
dispatcher_send(
|
|
|
|
self.hass, TUYA_DISCOVERY_NEW.format(domain), [device.id]
|
|
|
|
)
|
|
|
|
|
|
|
|
if device_add:
|
|
|
|
device_manager = self.hass.data[DOMAIN][self.entry.entry_id][
|
|
|
|
TUYA_DEVICE_MANAGER
|
|
|
|
]
|
|
|
|
device_manager.mq.stop()
|
|
|
|
tuya_mq = TuyaOpenMQ(device_manager.api)
|
|
|
|
tuya_mq.start()
|
|
|
|
|
|
|
|
device_manager.mq = tuya_mq
|
|
|
|
tuya_mq.add_message_listener(device_manager.on_message)
|
|
|
|
|
|
|
|
def remove_device(self, device_id: str) -> None:
|
|
|
|
"""Add device removed listener."""
|
|
|
|
_LOGGER.debug("tuya remove device:%s", device_id)
|
2021-10-12 03:32:15 +00:00
|
|
|
self.hass.add_job(async_remove_hass_device, self.hass, self.entry, device_id)
|