hass-core/homeassistant/components/light/zha.py
Russell Cloran 2f474a0ed8 zha: Handle both input and output clusters ()
bellows 0.3.0 changes the API to have both, renaming the attribute which used
to be for input clusters in the process.

This is in preparation for remotes.
2017-07-10 21:16:44 -07:00

183 lines
6.4 KiB
Python

"""
Lights on Zigbee Home Automation networks.
For more details on this platform, please refer to the documentation
at https://home-assistant.io/components/light.zha/
"""
import asyncio
import logging
from homeassistant.components import light, zha
from homeassistant.util.color import color_RGB_to_xy
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['zha']
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Zigbee Home Automation lights."""
discovery_info = zha.get_discovery_info(hass, discovery_info)
if discovery_info is None:
return
endpoint = discovery_info['endpoint']
try:
primaries = yield from endpoint.light_color['num_primaries']
discovery_info['num_primaries'] = primaries
except (AttributeError, KeyError):
pass
async_add_devices([Light(**discovery_info)], update_before_add=True)
class Light(zha.Entity, light.Light):
"""Representation of a ZHA or ZLL light."""
_domain = light.DOMAIN
def __init__(self, **kwargs):
"""Initialize the ZHA light."""
super().__init__(**kwargs)
self._supported_features = 0
self._color_temp = None
self._xy_color = None
self._brightness = None
import bellows.zigbee.zcl.clusters as zcl_clusters
if zcl_clusters.general.LevelControl.cluster_id in self._in_clusters:
self._supported_features |= light.SUPPORT_BRIGHTNESS
self._brightness = 0
if zcl_clusters.lighting.Color.cluster_id in self._in_clusters:
# Not sure all color lights necessarily support this directly
# Should we emulate it?
self._supported_features |= light.SUPPORT_COLOR_TEMP
# Silly heuristic, not sure if it works widely
if kwargs.get('num_primaries', 1) >= 3:
self._supported_features |= light.SUPPORT_XY_COLOR
self._supported_features |= light.SUPPORT_RGB_COLOR
self._xy_color = (1.0, 1.0)
@property
def is_on(self) -> bool:
"""Return true if entity is on."""
if self._state == 'unknown':
return False
return bool(self._state)
@asyncio.coroutine
def async_turn_on(self, **kwargs):
"""Turn the entity on."""
duration = 5 # tenths of s
if light.ATTR_COLOR_TEMP in kwargs:
temperature = kwargs[light.ATTR_COLOR_TEMP]
yield from self._endpoint.light_color.move_to_color_temp(
temperature, duration)
self._color_temp = temperature
if light.ATTR_XY_COLOR in kwargs:
self._xy_color = kwargs[light.ATTR_XY_COLOR]
elif light.ATTR_RGB_COLOR in kwargs:
xyb = color_RGB_to_xy(
*(int(val) for val in kwargs[light.ATTR_RGB_COLOR]))
self._xy_color = (xyb[0], xyb[1])
self._brightness = xyb[2]
if light.ATTR_XY_COLOR in kwargs or light.ATTR_RGB_COLOR in kwargs:
yield from self._endpoint.light_color.move_to_color(
int(self._xy_color[0] * 65535),
int(self._xy_color[1] * 65535),
duration,
)
if self._brightness is not None:
brightness = kwargs.get('brightness', self._brightness or 255)
self._brightness = brightness
# Move to level with on/off:
yield from self._endpoint.level.move_to_level_with_on_off(
brightness,
duration
)
self._state = 1
self.hass.async_add_job(self.async_update_ha_state())
return
yield from self._endpoint.on_off.on()
self._state = 1
self.hass.async_add_job(self.async_update_ha_state())
@asyncio.coroutine
def async_turn_off(self, **kwargs):
"""Turn the entity off."""
yield from self._endpoint.on_off.off()
self._state = 0
self.hass.async_add_job(self.async_update_ha_state())
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
return self._brightness
@property
def xy_color(self):
"""Return the XY color value [float, float]."""
return self._xy_color
@property
def color_temp(self):
"""Return the CT color value in mireds."""
return self._color_temp
@property
def supported_features(self):
"""Flag supported features."""
return self._supported_features
@asyncio.coroutine
def async_update(self):
"""Retrieve latest state."""
_LOGGER.debug("%s async_update", self.entity_id)
@asyncio.coroutine
def safe_read(cluster, attributes):
"""Swallow all exceptions from network read.
If we throw during initialization, setup fails. Rather have an
entity that exists, but is in a maybe wrong state, than no entity.
"""
try:
result, _ = yield from cluster.read_attributes(
attributes,
allow_cache=False,
)
return result
except Exception: # pylint: disable=broad-except
return {}
result = yield from safe_read(self._endpoint.on_off, ['on_off'])
self._state = result.get('on_off', self._state)
if self._supported_features & light.SUPPORT_BRIGHTNESS:
result = yield from safe_read(self._endpoint.level,
['current_level'])
self._brightness = result.get('current_level', self._brightness)
if self._supported_features & light.SUPPORT_COLOR_TEMP:
result = yield from safe_read(self._endpoint.light_color,
['color_temperature'])
self._color_temp = result.get('color_temperature',
self._color_temp)
if self._supported_features & light.SUPPORT_XY_COLOR:
result = yield from safe_read(self._endpoint.light_color,
['current_x', 'current_y'])
if 'current_x' in result and 'current_y' in result:
self._xy_color = (result['current_x'], result['current_y'])
@property
def should_poll(self) -> bool:
"""Return True if entity has to be polled for state.
False if entity pushes its state to HA.
"""
return False