core/homeassistant/components/light/hue.py

371 lines
12 KiB
Python
Raw Normal View History

2015-08-11 12:55:30 +00:00
"""
This component provides light support for the Philips Hue system.
2015-10-26 06:12:00 +00:00
2015-11-09 12:12:18 +00:00
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.hue/
2015-08-11 12:55:30 +00:00
"""
import asyncio
from datetime import timedelta
import logging
import random
import async_timeout
from homeassistant.components import hue
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH,
ATTR_TRANSITION, ATTR_HS_COLOR, EFFECT_COLORLOOP, EFFECT_RANDOM,
FLASH_LONG, FLASH_SHORT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP,
SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_COLOR, SUPPORT_TRANSITION,
Light)
from homeassistant.util import color
DEPENDENCIES = ['hue']
SCAN_INTERVAL = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
SUPPORT_HUE_ON_OFF = (SUPPORT_FLASH | SUPPORT_TRANSITION)
SUPPORT_HUE_DIMMABLE = (SUPPORT_HUE_ON_OFF | SUPPORT_BRIGHTNESS)
SUPPORT_HUE_COLOR_TEMP = (SUPPORT_HUE_DIMMABLE | SUPPORT_COLOR_TEMP)
SUPPORT_HUE_COLOR = (SUPPORT_HUE_DIMMABLE | SUPPORT_EFFECT | SUPPORT_COLOR)
SUPPORT_HUE_EXTENDED = (SUPPORT_HUE_COLOR_TEMP | SUPPORT_HUE_COLOR)
SUPPORT_HUE = {
'Extended color light': SUPPORT_HUE_EXTENDED,
'Color light': SUPPORT_HUE_COLOR,
'Dimmable light': SUPPORT_HUE_DIMMABLE,
'On/Off plug-in unit': SUPPORT_HUE_ON_OFF,
'Color temperature light': SUPPORT_HUE_COLOR_TEMP
}
ATTR_IS_HUE_GROUP = 'is_hue_group'
# Minimum Hue Bridge API version to support groups
# 1.4.0 introduced extended group info
# 1.12 introduced the state object for groups
# 1.13 introduced "any_on" to group state objects
GROUP_MIN_API_VERSION = (1, 13, 0)
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Old way of setting up Hue lights.
Can only be called when a user accidentally mentions hue platform in their
config. But even in that case it would have been ignored.
"""
pass
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the Hue lights from a config entry."""
bridge = hass.data[hue.DOMAIN][config_entry.data['host']]
cur_lights = {}
cur_groups = {}
api_version = tuple(
int(v) for v in bridge.api.config.apiversion.split('.'))
allow_groups = bridge.allow_groups
if allow_groups and api_version < GROUP_MIN_API_VERSION:
_LOGGER.warning('Please update your Hue bridge to support groups')
allow_groups = False
# Hue updates all lights via a single API call.
#
# If we call a service to update 2 lights, we only want the API to be
# called once.
#
# The throttle decorator will return right away if a call is currently
# in progress. This means that if we are updating 2 lights, the first one
# is in the update method, the second one will skip it and assume the
# update went through and updates it's data, not good!
#
# The current mechanism will make sure that all lights will wait till
# the update call is done before writing their data to the state machine.
#
# An alternative approach would be to disable automatic polling by Home
# Assistant and take control ourselves. This works great for polling as now
# we trigger from 1 time update an update to all entities. However it gets
# tricky from inside async_turn_on and async_turn_off.
#
# If automatic polling is enabled, Home Assistant will call the entity
# update method after it is done calling all the services. This means that
# when we update, we know all commands have been processed. If we trigger
# the update from inside async_turn_on, the update will not capture the
# changes to the second entity until the next polling update because the
# throttle decorator will prevent the call.
progress = None
light_progress = set()
group_progress = set()
async def request_update(is_group, object_id):
"""Request an update.
We will only make 1 request to the server for updating at a time. If a
request is in progress, we will join the request that is in progress.
This approach is possible because should_poll=True. That means that
Home Assistant will ask lights for updates during a polling cycle or
after it has called a service.
We keep track of the lights that are waiting for the request to finish.
When new data comes in, we'll trigger an update for all non-waiting
lights. This covers the case where a service is called to enable 2
lights but in the meanwhile some other light has changed too.
"""
nonlocal progress
progress_set = group_progress if is_group else light_progress
progress_set.add(object_id)
if progress is not None:
return await progress
progress = asyncio.ensure_future(update_bridge())
result = await progress
progress = None
light_progress.clear()
group_progress.clear()
return result
async def update_bridge():
"""Update the values of the bridge.
Will update lights and, if enabled, groups from the bridge.
"""
tasks = []
tasks.append(async_update_items(
hass, bridge, async_add_devices, request_update,
False, cur_lights, light_progress
))
if allow_groups:
tasks.append(async_update_items(
hass, bridge, async_add_devices, request_update,
True, cur_groups, group_progress
))
await asyncio.wait(tasks)
await update_bridge()
async def async_update_items(hass, bridge, async_add_devices,
request_bridge_update, is_group, current,
progress_waiting):
"""Update either groups or lights from the bridge."""
import aiohue
if is_group:
api = bridge.api.groups
else:
api = bridge.api.lights
try:
with async_timeout.timeout(4):
await api.update()
except (asyncio.TimeoutError, aiohue.AiohueException):
if not bridge.available:
return
_LOGGER.error('Unable to reach bridge %s', bridge.host)
bridge.available = False
for light_id, light in current.items():
if light_id not in progress_waiting:
light.async_schedule_update_ha_state()
return
if not bridge.available:
_LOGGER.info('Reconnected to bridge %s', bridge.host)
bridge.available = True
new_lights = []
for item_id in api:
if item_id not in current:
current[item_id] = HueLight(
api[item_id], request_bridge_update, bridge, is_group)
new_lights.append(current[item_id])
elif item_id not in progress_waiting:
current[item_id].async_schedule_update_ha_state()
if new_lights:
async_add_devices(new_lights)
2015-06-13 23:42:09 +00:00
class HueLight(Light):
2016-03-07 21:08:21 +00:00
"""Representation of a Hue light."""
def __init__(self, light, request_bridge_update, bridge, is_group=False):
2016-03-07 21:08:21 +00:00
"""Initialize the light."""
self.light = light
self.async_request_bridge_update = request_bridge_update
self.bridge = bridge
self.is_group = is_group
if is_group:
self.is_osram = False
self.is_philips = False
else:
self.is_osram = light.manufacturername == 'OSRAM'
self.is_philips = light.manufacturername == 'Philips'
@property
def unique_id(self):
2016-03-07 21:08:21 +00:00
"""Return the ID of this Hue light."""
return self.light.uniqueid
@property
def name(self):
"""Return the name of the Hue light."""
return self.light.name
@property
2015-06-13 23:42:09 +00:00
def brightness(self):
2016-03-07 21:08:21 +00:00
"""Return the brightness of this light between 0..255."""
if self.is_group:
return self.light.action.get('bri')
return self.light.state.get('bri')
@property
def _color_mode(self):
"""Return the hue color mode."""
if self.is_group:
return self.light.action.get('colormode')
return self.light.state.get('colormode')
2015-06-13 23:42:09 +00:00
@property
def hs_color(self):
"""Return the hs color value."""
mode = self._color_mode
source = self.light.action if self.is_group else self.light.state
if mode in ('xy', 'hs') and 'xy' in source:
return color.color_xy_to_hs(*source['xy'])
return None
@property
def color_temp(self):
2016-03-07 21:08:21 +00:00
"""Return the CT color value."""
# Don't return color temperature unless in color temperature mode
if self._color_mode != "ct":
return None
if self.is_group:
return self.light.action.get('ct')
return self.light.state.get('ct')
@property
def is_on(self):
2016-03-07 21:08:21 +00:00
"""Return true if device is on."""
if self.is_group:
return self.light.state['any_on']
return self.light.state['on']
@property
def available(self):
"""Return if light is available."""
return self.bridge.available and (self.is_group or
self.bridge.allow_unreachable or
self.light.state['reachable'])
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_HUE.get(self.light.type, SUPPORT_HUE_EXTENDED)
2017-02-17 18:37:45 +00:00
@property
def effect_list(self):
"""Return the list of supported effects."""
return [EFFECT_COLORLOOP, EFFECT_RANDOM]
async def async_turn_on(self, **kwargs):
2016-03-07 21:08:21 +00:00
"""Turn the specified or all lights on."""
command = {'on': True}
2014-11-26 05:28:43 +00:00
if ATTR_TRANSITION in kwargs:
command['transitiontime'] = int(kwargs[ATTR_TRANSITION] * 10)
if ATTR_HS_COLOR in kwargs:
if self.is_osram:
command['hue'] = int(kwargs[ATTR_HS_COLOR][0] / 360 * 65535)
command['sat'] = int(kwargs[ATTR_HS_COLOR][1] / 100 * 255)
else:
# Philips hue bulb models respond differently to hue/sat
# requests, so we convert to XY first to ensure a consistent
# color.
command['xy'] = color.color_hs_to_xy(*kwargs[ATTR_HS_COLOR])
elif ATTR_COLOR_TEMP in kwargs:
temp = kwargs[ATTR_COLOR_TEMP]
command['ct'] = max(self.min_mireds, min(temp, self.max_mireds))
if ATTR_BRIGHTNESS in kwargs:
command['bri'] = kwargs[ATTR_BRIGHTNESS]
flash = kwargs.get(ATTR_FLASH)
if flash == FLASH_LONG:
command['alert'] = 'lselect'
del command['on']
elif flash == FLASH_SHORT:
command['alert'] = 'select'
del command['on']
Add deCONZ component (#10321) * Base implementation of component, no sensors yet * Added senor files * First fully working chain of sensors and binary sensors going from hardware in to hass * Clean up * Clean up * Added light platform * Turning lights on and off and set brightness now works * Pydeconz is now a proper pypi package Stop sessions when Home Assistant is shutting down Use a simpler websocket client * Updated pydocstrings Followed recommendations from pylint and flake8 * Clean up * Updated requirements_all.txt * Updated Codeowners to include deconz.py Also re-added the Axis component since it had gotten removed * Bump requirement * Bumped to v2 Reran script/gen_requirements * Removed global DECONZ since it wasn't relevant any more * Username and password is only relevant in the context of getting a API key * Add support for additional sensors * Added support for groups * Moved import of component library to inside of methods * Moved the need for device id to library * Bump pydeconz to v5 * Add support for colored lights * Pylint and flake8 import improvements * DATA_DECONZ TO DECONZ_DATA * Add support for transition time * Add support for flash * Bump to v7 * ZHASwitch devices will now only generate events by default, instead of being a sensor entity * Clean up * Add battery sensor when device signals through an event * Third-party library communicates with service * Add support for effect colorloop * Bump to pydeconz v8 * Same domain everywhere * Clean up * Updated requirements_all * Generated API key will now be stored in a config file * Change battery sensor to register to callback since library now supports multiple callbacks Move DeconzEvent to hub Bump to v9 * Improve entity attributes * Change end of battery name to battery level No need for static icon variable when using battery level helper * Bump requirement to v10 * Improve pydocstring for DeconzEvent Rename TYPE_AS_EVENT to CONF_TYPE_AS_EVENT * Allow separate brightness to override RGB brightness * Expose device.reachable in entity available property * Bump requirement to 11 (it goes up to 11!) * Pylint comment * Binary sensors don't have unit of measurement * Removed service to generate API key in favor of just generating it as a last resort of no API key is specified in configuration.yaml or deconz.conf * Replace clear text to attribute definitions * Use more constants * Bump requirements to v12 * Color temp requires xy color support * Only ZHASwitch should be an event * Bump requirements to v13 * Added effect_list property * Add attribute to battery sensor to easy find event id * Bump requirements to v14 * Fix hound comment * Bumped requirements_all information to v14 * Add service to configure devices on deCONZ * Add initial support for scenes * Bump requirements to v15 * Fix review comments * Python doc string improvement * Improve setup and error handling during setup * Changed how to evaluate light features * Remove 'ghost' events by not triggering updates if the signal originates from a config event Bump requirement to v17 * Fix pylint issue by moving scene ownership in to groups in requirement pydeconz Bump requirement to v18 * Added configurator option to register to deCONZ when unlocking gateway through settings Bump requirement to v20 * Improve async configurator * No user interaction for deconz.conf * No file management in event loop * Improve readability of load platform * Fewer entity attributes * Use values() instead of items() for dicts where applicable * Do one add devices per platform * Clean up of unused attributes * Make sure that discovery info is not None * Only register configure service and shutdown service when deconz has been setup properly * Move description * Fix lines longer than 80 * Moved deconz services to a separate file and moved hub to deconz/__init__.py * Remove option to configure switch as entity * Moved DeconzEvent to sensor since it is only Switch buttonpress that will be sent as event * Added support for automatic discovery of deconz Thanks to Kroimon for adding support to netdisco * Use markup for configuration description * Fix coveragerc * Remove deCONZ support from Hue component * Improved docstrings and readability * Remove unnecessary extra name for storing in hass.data, using domain instead * Improve readability by renaming all async methods Bump to v21 - improved async naming on methods * Fix first line not being in imperative mood * Added logo to configurator Let deconz.conf be visible since it will be the main config for the component after initial setup * Removed bridge_type from new unit tests as part of removing deconz support from hue component * Capitalize first letters of Battery Level * Properly update state of sensor as well as reachable and battery Bump dependency to v22 * Fix flake8 Multi-line docstring closing quotes should be on a separate line * Fix martinhjelmares comments Bump dependency to v23 Use only HASS aiohttp session Change when to use 'deconz' or domain or deconz data Clean up unused logger defines Remove unnecessary return values Fix faulty references to component documentation Move callback registration to after entity has been initialized by HASS Less inception style on pydocs ;) Simplify loading platforms by using a for loop Added voluptous schema for service Yaml file is for deconz only, no need to have the domain present Remove domain constraint when creating event title
2018-01-01 16:08:13 +00:00
else:
command['alert'] = 'none'
effect = kwargs.get(ATTR_EFFECT)
if effect == EFFECT_COLORLOOP:
command['effect'] = 'colorloop'
elif effect == EFFECT_RANDOM:
2015-12-25 03:35:36 +00:00
command['hue'] = random.randrange(0, 65535)
command['sat'] = random.randrange(150, 254)
elif self.is_philips:
command['effect'] = 'none'
if self.is_group:
await self.light.set_action(**command)
else:
await self.light.set_state(**command)
async def async_turn_off(self, **kwargs):
2016-03-07 21:08:21 +00:00
"""Turn the specified or all lights off."""
command = {'on': False}
2014-11-26 05:28:43 +00:00
if ATTR_TRANSITION in kwargs:
command['transitiontime'] = int(kwargs[ATTR_TRANSITION] * 10)
flash = kwargs.get(ATTR_FLASH)
if flash == FLASH_LONG:
command['alert'] = 'lselect'
del command['on']
elif flash == FLASH_SHORT:
command['alert'] = 'select'
del command['on']
Add deCONZ component (#10321) * Base implementation of component, no sensors yet * Added senor files * First fully working chain of sensors and binary sensors going from hardware in to hass * Clean up * Clean up * Added light platform * Turning lights on and off and set brightness now works * Pydeconz is now a proper pypi package Stop sessions when Home Assistant is shutting down Use a simpler websocket client * Updated pydocstrings Followed recommendations from pylint and flake8 * Clean up * Updated requirements_all.txt * Updated Codeowners to include deconz.py Also re-added the Axis component since it had gotten removed * Bump requirement * Bumped to v2 Reran script/gen_requirements * Removed global DECONZ since it wasn't relevant any more * Username and password is only relevant in the context of getting a API key * Add support for additional sensors * Added support for groups * Moved import of component library to inside of methods * Moved the need for device id to library * Bump pydeconz to v5 * Add support for colored lights * Pylint and flake8 import improvements * DATA_DECONZ TO DECONZ_DATA * Add support for transition time * Add support for flash * Bump to v7 * ZHASwitch devices will now only generate events by default, instead of being a sensor entity * Clean up * Add battery sensor when device signals through an event * Third-party library communicates with service * Add support for effect colorloop * Bump to pydeconz v8 * Same domain everywhere * Clean up * Updated requirements_all * Generated API key will now be stored in a config file * Change battery sensor to register to callback since library now supports multiple callbacks Move DeconzEvent to hub Bump to v9 * Improve entity attributes * Change end of battery name to battery level No need for static icon variable when using battery level helper * Bump requirement to v10 * Improve pydocstring for DeconzEvent Rename TYPE_AS_EVENT to CONF_TYPE_AS_EVENT * Allow separate brightness to override RGB brightness * Expose device.reachable in entity available property * Bump requirement to 11 (it goes up to 11!) * Pylint comment * Binary sensors don't have unit of measurement * Removed service to generate API key in favor of just generating it as a last resort of no API key is specified in configuration.yaml or deconz.conf * Replace clear text to attribute definitions * Use more constants * Bump requirements to v12 * Color temp requires xy color support * Only ZHASwitch should be an event * Bump requirements to v13 * Added effect_list property * Add attribute to battery sensor to easy find event id * Bump requirements to v14 * Fix hound comment * Bumped requirements_all information to v14 * Add service to configure devices on deCONZ * Add initial support for scenes * Bump requirements to v15 * Fix review comments * Python doc string improvement * Improve setup and error handling during setup * Changed how to evaluate light features * Remove 'ghost' events by not triggering updates if the signal originates from a config event Bump requirement to v17 * Fix pylint issue by moving scene ownership in to groups in requirement pydeconz Bump requirement to v18 * Added configurator option to register to deCONZ when unlocking gateway through settings Bump requirement to v20 * Improve async configurator * No user interaction for deconz.conf * No file management in event loop * Improve readability of load platform * Fewer entity attributes * Use values() instead of items() for dicts where applicable * Do one add devices per platform * Clean up of unused attributes * Make sure that discovery info is not None * Only register configure service and shutdown service when deconz has been setup properly * Move description * Fix lines longer than 80 * Moved deconz services to a separate file and moved hub to deconz/__init__.py * Remove option to configure switch as entity * Moved DeconzEvent to sensor since it is only Switch buttonpress that will be sent as event * Added support for automatic discovery of deconz Thanks to Kroimon for adding support to netdisco * Use markup for configuration description * Fix coveragerc * Remove deCONZ support from Hue component * Improved docstrings and readability * Remove unnecessary extra name for storing in hass.data, using domain instead * Improve readability by renaming all async methods Bump to v21 - improved async naming on methods * Fix first line not being in imperative mood * Added logo to configurator Let deconz.conf be visible since it will be the main config for the component after initial setup * Removed bridge_type from new unit tests as part of removing deconz support from hue component * Capitalize first letters of Battery Level * Properly update state of sensor as well as reachable and battery Bump dependency to v22 * Fix flake8 Multi-line docstring closing quotes should be on a separate line * Fix martinhjelmares comments Bump dependency to v23 Use only HASS aiohttp session Change when to use 'deconz' or domain or deconz data Clean up unused logger defines Remove unnecessary return values Fix faulty references to component documentation Move callback registration to after entity has been initialized by HASS Less inception style on pydocs ;) Simplify loading platforms by using a for loop Added voluptous schema for service Yaml file is for deconz only, no need to have the domain present Remove domain constraint when creating event title
2018-01-01 16:08:13 +00:00
else:
command['alert'] = 'none'
if self.is_group:
await self.light.set_action(**command)
else:
await self.light.set_state(**command)
async def async_update(self):
2016-03-07 21:08:21 +00:00
"""Synchronize state with bridge."""
await self.async_request_bridge_update(self.is_group, self.light.id)
@property
def device_state_attributes(self):
"""Return the device state attributes."""
attributes = {}
if self.is_group:
attributes[ATTR_IS_HUE_GROUP] = self.is_group
return attributes