160 lines
5.2 KiB
Python
160 lines
5.2 KiB
Python
"""Component to embed TP-Link smart home devices."""
|
|
import logging
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.const import CONF_HOST
|
|
from homeassistant import config_entries
|
|
from homeassistant.helpers import config_entry_flow
|
|
import homeassistant.helpers.config_validation as cv
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
DOMAIN = 'tplink'
|
|
|
|
TPLINK_HOST_SCHEMA = vol.Schema({
|
|
vol.Required(CONF_HOST): cv.string
|
|
})
|
|
|
|
CONF_LIGHT = 'light'
|
|
CONF_SWITCH = 'switch'
|
|
CONF_DISCOVERY = 'discovery'
|
|
|
|
ATTR_CONFIG = 'config'
|
|
|
|
CONFIG_SCHEMA = vol.Schema({
|
|
DOMAIN: vol.Schema({
|
|
vol.Optional('light', default=[]): vol.All(cv.ensure_list,
|
|
[TPLINK_HOST_SCHEMA]),
|
|
vol.Optional('switch', default=[]): vol.All(cv.ensure_list,
|
|
[TPLINK_HOST_SCHEMA]),
|
|
vol.Optional('discovery', default=True): cv.boolean,
|
|
}),
|
|
}, extra=vol.ALLOW_EXTRA)
|
|
|
|
REQUIREMENTS = ['pyHS100==0.3.4']
|
|
|
|
|
|
async def _async_has_devices(hass):
|
|
"""Return if there are devices that can be discovered."""
|
|
from pyHS100 import Discover
|
|
|
|
def discover():
|
|
devs = Discover.discover()
|
|
return devs
|
|
return await hass.async_add_executor_job(discover)
|
|
|
|
|
|
async def async_setup(hass, config):
|
|
"""Set up the TP-Link component."""
|
|
conf = config.get(DOMAIN)
|
|
|
|
hass.data[DOMAIN] = {}
|
|
hass.data[DOMAIN][ATTR_CONFIG] = conf
|
|
|
|
if conf is not None:
|
|
hass.async_create_task(hass.config_entries.flow.async_init(
|
|
DOMAIN, context={'source': config_entries.SOURCE_IMPORT}))
|
|
|
|
return True
|
|
|
|
|
|
async def async_setup_entry(hass, config_entry):
|
|
"""Set up TPLink from a config entry."""
|
|
from pyHS100 import SmartBulb, SmartPlug, SmartDeviceException
|
|
|
|
devices = {}
|
|
|
|
config_data = hass.data[DOMAIN].get(ATTR_CONFIG)
|
|
|
|
# These will contain the initialized devices
|
|
lights = hass.data[DOMAIN][CONF_LIGHT] = []
|
|
switches = hass.data[DOMAIN][CONF_SWITCH] = []
|
|
|
|
# If discovery is defined and not disabled, discover devices
|
|
# If initialized from configure integrations, there's no config
|
|
# so we default here to True
|
|
if config_data is None or config_data[CONF_DISCOVERY]:
|
|
devs = await _async_has_devices(hass)
|
|
_LOGGER.info("Discovered %s TP-Link smart home device(s)", len(devs))
|
|
devices.update(devs)
|
|
|
|
def _device_for_type(host, type_):
|
|
dev = None
|
|
if type_ == CONF_LIGHT:
|
|
dev = SmartBulb(host)
|
|
elif type_ == CONF_SWITCH:
|
|
dev = SmartPlug(host)
|
|
|
|
return dev
|
|
|
|
# When arriving from configure integrations, we have no config data.
|
|
if config_data is not None:
|
|
for type_ in [CONF_LIGHT, CONF_SWITCH]:
|
|
for entry in config_data[type_]:
|
|
try:
|
|
host = entry['host']
|
|
dev = _device_for_type(host, type_)
|
|
devices[host] = dev
|
|
_LOGGER.debug("Succesfully added %s %s: %s",
|
|
type_, host, dev)
|
|
except SmartDeviceException as ex:
|
|
_LOGGER.error("Unable to initialize %s %s: %s",
|
|
type_, host, ex)
|
|
|
|
# This is necessary to avoid I/O blocking on is_dimmable
|
|
def _fill_device_lists():
|
|
for dev in devices.values():
|
|
if isinstance(dev, SmartPlug):
|
|
try:
|
|
if dev.is_dimmable: # Dimmers act as lights
|
|
lights.append(dev)
|
|
else:
|
|
switches.append(dev)
|
|
except SmartDeviceException as ex:
|
|
_LOGGER.error("Unable to connect to device %s: %s",
|
|
dev.host, ex)
|
|
|
|
elif isinstance(dev, SmartBulb):
|
|
lights.append(dev)
|
|
else:
|
|
_LOGGER.error("Unknown smart device type: %s", type(dev))
|
|
|
|
# Avoid blocking on is_dimmable
|
|
await hass.async_add_executor_job(_fill_device_lists)
|
|
|
|
forward_setup = hass.config_entries.async_forward_entry_setup
|
|
if lights:
|
|
_LOGGER.debug("Got %s lights: %s", len(lights), lights)
|
|
hass.async_create_task(forward_setup(config_entry, 'light'))
|
|
if switches:
|
|
_LOGGER.debug("Got %s switches: %s", len(switches), switches)
|
|
hass.async_create_task(forward_setup(config_entry, 'switch'))
|
|
|
|
return True
|
|
|
|
|
|
async def async_unload_entry(hass, entry):
|
|
"""Unload a config entry."""
|
|
forward_unload = hass.config_entries.async_forward_entry_unload
|
|
remove_lights = remove_switches = False
|
|
if hass.data[DOMAIN][CONF_LIGHT]:
|
|
remove_lights = await forward_unload(entry, 'light')
|
|
if hass.data[DOMAIN][CONF_SWITCH]:
|
|
remove_switches = await forward_unload(entry, 'switch')
|
|
|
|
if remove_lights or remove_switches:
|
|
hass.data[DOMAIN].clear()
|
|
return True
|
|
|
|
# We were not able to unload the platforms, either because there
|
|
# were none or one of the forward_unloads failed.
|
|
return False
|
|
|
|
|
|
config_entry_flow.register_discovery_flow(DOMAIN,
|
|
'TP-Link Smart Home',
|
|
_async_has_devices,
|
|
config_entries.CONN_CLASS_LOCAL_POLL)
|