diff --git a/CODEOWNERS b/CODEOWNERS index 6cdfa6a45d5..603a6467d5b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -63,7 +63,6 @@ homeassistant/components/device_tracker/huawei_router.py @abmantis homeassistant/components/device_tracker/tile.py @bachya homeassistant/components/history_graph.py @andrey-git homeassistant/components/influx.py @fabaff -homeassistant/components/light/lifx.py @amelchio homeassistant/components/light/lifx_legacy.py @amelchio homeassistant/components/light/tplink.py @rytilahti homeassistant/components/light/yeelight.py @rytilahti @@ -180,6 +179,10 @@ homeassistant/components/*/knx.py @Julius2342 homeassistant/components/konnected.py @heythisisnate homeassistant/components/*/konnected.py @heythisisnate +# L +homeassistant/components/lifx.py @amelchio +homeassistant/components/*/lifx.py @amelchio + # M homeassistant/components/matrix.py @tinloaf homeassistant/components/*/matrix.py @tinloaf diff --git a/homeassistant/components/lifx/.translations/en.json b/homeassistant/components/lifx/.translations/en.json new file mode 100644 index 00000000000..64fdc7516ea --- /dev/null +++ b/homeassistant/components/lifx/.translations/en.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "no_devices_found": "No LIFX devices found on the network.", + "single_instance_allowed": "Only a single configuration of LIFX is possible." + }, + "step": { + "confirm": { + "description": "Do you want to set up LIFX?", + "title": "LIFX" + } + }, + "title": "LIFX" + } +} \ No newline at end of file diff --git a/homeassistant/components/lifx/__init__.py b/homeassistant/components/lifx/__init__.py new file mode 100644 index 00000000000..85e249eb934 --- /dev/null +++ b/homeassistant/components/lifx/__init__.py @@ -0,0 +1,97 @@ +"""Component to embed LIFX.""" +import asyncio +import socket + +import async_timeout +import voluptuous as vol +import homeassistant.helpers.config_validation as cv + +from homeassistant import config_entries +from homeassistant.helpers import config_entry_flow +from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN + + +DOMAIN = 'lifx' +REQUIREMENTS = ['aiolifx==0.6.3'] + +CONF_SERVER = 'server' +CONF_BROADCAST = 'broadcast' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: { + LIGHT_DOMAIN: { + vol.Optional(CONF_SERVER): cv.string, + vol.Optional(CONF_BROADCAST): cv.string, + } + } +}, extra=vol.ALLOW_EXTRA) + + +async def async_setup(hass, config): + """Set up the LIFX component.""" + conf = config.get(DOMAIN) + + hass.data[DOMAIN] = conf or {} + + 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, entry): + """Set up LIFX from a config entry.""" + hass.async_create_task(hass.config_entries.async_forward_entry_setup( + entry, LIGHT_DOMAIN)) + return True + + +async def _async_has_devices(hass): + """Return if there are devices that can be discovered.""" + import aiolifx + + manager = DiscoveryManager() + lifx_discovery = aiolifx.LifxDiscovery(hass.loop, manager) + coro = hass.loop.create_datagram_endpoint( + lambda: lifx_discovery, + family=socket.AF_INET) + hass.async_create_task(coro) + + has_devices = await manager.found_devices() + lifx_discovery.cleanup() + + return has_devices + + +config_entry_flow.register_discovery_flow( + DOMAIN, 'LIFX', _async_has_devices, config_entries.CONN_CLASS_LOCAL_POLL) + + +class DiscoveryManager: + """Temporary LIFX manager for discovering any bulb.""" + + def __init__(self): + """Initialize the manager.""" + self._event = asyncio.Event() + + async def found_devices(self): + """Return whether any device could be discovered.""" + try: + async with async_timeout.timeout(2): + await self._event.wait() + + # Let bulbs recover from the discovery + await asyncio.sleep(1) + + return True + except asyncio.TimeoutError: + return False + + def register(self, bulb): + """Handle aiolifx detected bulb.""" + self._event.set() + + def unregister(self, bulb): + """Handle aiolifx disappearing bulbs.""" + pass diff --git a/homeassistant/components/lifx/strings.json b/homeassistant/components/lifx/strings.json new file mode 100644 index 00000000000..300c9b628f3 --- /dev/null +++ b/homeassistant/components/lifx/strings.json @@ -0,0 +1,15 @@ +{ + "config": { + "title": "LIFX", + "step": { + "confirm": { + "title": "LIFX", + "description": "Do you want to set up LIFX?" + } + }, + "abort": { + "single_instance_allowed": "Only a single configuration of LIFX is possible.", + "no_devices_found": "No LIFX devices found on the network." + } + } +} diff --git a/homeassistant/components/light/lifx.py b/homeassistant/components/light/lifx.py index 9dcd2ae4cc2..87b3b02dd16 100644 --- a/homeassistant/components/light/lifx.py +++ b/homeassistant/components/light/lifx.py @@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.lifx/ """ import asyncio +import socket from datetime import timedelta from functools import partial import logging @@ -17,10 +18,12 @@ from homeassistant import util from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, ATTR_COLOR_NAME, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR, ATTR_KELVIN, ATTR_RGB_COLOR, ATTR_TRANSITION, - ATTR_XY_COLOR, COLOR_GROUP, DOMAIN, LIGHT_TURN_ON_SCHEMA, PLATFORM_SCHEMA, + ATTR_XY_COLOR, COLOR_GROUP, DOMAIN, LIGHT_TURN_ON_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_TRANSITION, VALID_BRIGHTNESS, VALID_BRIGHTNESS_PCT, Light, preprocess_turn_on_alternatives) +from homeassistant.components.lifx import ( + DOMAIN as LIFX_DOMAIN, CONF_SERVER, CONF_BROADCAST) from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback import homeassistant.helpers.config_validation as cv @@ -30,23 +33,16 @@ import homeassistant.util.color as color_util _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['aiolifx==0.6.3', 'aiolifx_effects==0.2.1'] +DEPENDENCIES = ['lifx'] +REQUIREMENTS = ['aiolifx_effects==0.2.1'] -UDP_BROADCAST_PORT = 56700 +SCAN_INTERVAL = timedelta(seconds=10) DISCOVERY_INTERVAL = 60 MESSAGE_TIMEOUT = 1.0 MESSAGE_RETRIES = 8 UNAVAILABLE_GRACE = 90 -CONF_SERVER = 'server' -CONF_BROADCAST = 'broadcast' - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_SERVER, default='0.0.0.0'): cv.string, - vol.Optional(CONF_BROADCAST, default='255.255.255.255'): cv.string, -}) - SERVICE_LIFX_SET_STATE = 'lifx_set_state' ATTR_INFRARED = 'infrared' @@ -138,24 +134,33 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the LIFX platform.""" + """Set up the LIFX light platform. Obsolete.""" + _LOGGER.warning('LIFX no longer works with light platform configuration.') + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up LIFX from a config entry.""" if sys.platform == 'win32': _LOGGER.warning("The lifx platform is known to not work on Windows. " "Consider using the lifx_legacy platform instead") - server_addr = config.get(CONF_SERVER) + config = hass.data[LIFX_DOMAIN].get(DOMAIN, {}) lifx_manager = LIFXManager(hass, async_add_entities) - lifx_discovery = aiolifx().LifxDiscovery( - hass.loop, - lifx_manager, - discovery_interval=DISCOVERY_INTERVAL, - broadcast_ip=config.get(CONF_BROADCAST)) - coro = hass.loop.create_datagram_endpoint( - lambda: lifx_discovery, local_addr=(server_addr, UDP_BROADCAST_PORT)) + broadcast_ip = config.get(CONF_BROADCAST) + kwargs = {'discovery_interval': DISCOVERY_INTERVAL} + if broadcast_ip: + kwargs['broadcast_ip'] = broadcast_ip + lifx_discovery = aiolifx().LifxDiscovery(hass.loop, lifx_manager, **kwargs) - hass.async_add_job(coro) + kwargs = {'family': socket.AF_INET} + local_addr = config.get(CONF_SERVER) + if local_addr is not None: + kwargs['local_addr'] = (local_addr, 0) + coro = hass.loop.create_datagram_endpoint(lambda: lifx_discovery, **kwargs) + + hass.async_create_task(coro) @callback def cleanup(event): @@ -225,7 +230,7 @@ class LIFXManager: for light in self.service_to_entities(service): if service.service == SERVICE_LIFX_SET_STATE: task = light.set_state(**service.data) - tasks.append(self.hass.async_add_job(task)) + tasks.append(self.hass.async_create_task(task)) if tasks: await asyncio.wait(tasks, loop=self.hass.loop) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 1cc2e1362af..a5c44c30ce7 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -142,6 +142,7 @@ FLOWS = [ 'hue', 'ifttt', 'ios', + 'lifx', 'mqtt', 'nest', 'openuv', diff --git a/requirements_all.txt b/requirements_all.txt index 5c5415d9ec6..01d02379371 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -106,7 +106,7 @@ aiohue==1.5.0 # homeassistant.components.sensor.imap aioimaplib==0.7.13 -# homeassistant.components.light.lifx +# homeassistant.components.lifx aiolifx==0.6.3 # homeassistant.components.light.lifx