mirror of
https://github.com/home-assistant/core.git
synced 2025-06-01 19:57:07 +00:00

* {switch,light}.tplink: use deviceid as unique id, fetch name from the device during initialization * raise PlatformNotReady when no device is available * Use mac instead of deviceid * remove name option as obsolete * Add support for configuration flow / integration Allows activating automatic discovery of supported devices from the configuration * Fix linting, update requirements_all.txt * start cleaning up tplink component based on feedback * add device info, improve config handling * Allow overriding detected devices via configuration file * Update requirements.txt * Remove debug logging * make hound happy * Avoid I/O during init and simplify the code, remove remains of leds_on * Fix issues based on feedback, use consistent quotation marks for device info * add async_setup_platform emiting a deprecation warning * Avoid blocking the I/O, check for None on features * handle some Martin's comments, schema-validation is still missing * use async_create_task instead of async_add_job, let core validate the schema * simplify configuration handling by storing the configuration data separately from initialized instances * add default values to schema, make hound happy * with defaults set by schema, simplify the checks. add async_unload_entry * Use constant for data structure access * REWORD add a short note about async_unload_entry * handle feedback from Martin, config_data is checked against Noneness * use pop to remove the domain on unload * First steps to add tests for the new tplink component * embed platforms under the component directory * Fix tests by mocking the pyhs100 internals * Fix linting * Test against multiple instances of devices, tidy up * (hopefully) final linting round * Add pyHS100 to test requirements * log always the warnings occured during an update to make them easy to see * revert back the warning behavior (requirement for silver level in IQS) * Unload only when an entry is being loaded and add tests for that Thanks @MartinHjelmare for pointing this out! * Fix linting * Bump the upstream lib, fixes most prominently the HSV setting on bulbs * Test unloading for all platforms, clear the data storage instead of popping it out, making it possible to reconfigure after removal without restarting hass first * Use class variables instead of instance variables for bulb states, required for HS220 * Use new-style format string * Fix indenting, uppercase the mock constant * Run black on test_init, hopefully that will finally fix the weird formatting (pycharm, pylint and hound seems to have different opinions...)
155 lines
5.0 KiB
Python
155 lines
5.0 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):
|
|
if dev.is_dimmable: # Dimmers act as lights
|
|
lights.append(dev)
|
|
else:
|
|
switches.append(dev)
|
|
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)
|