-
-
Notifications
You must be signed in to change notification settings - Fork 33.8k
Async support with resource observation. #7815
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fb921f3
140cd0b
416559b
976527c
ebc408a
304857f
264b43c
b83ce62
836bb63
93bdbd0
6a13613
b1e1fc2
089c65e
b6ed0ed
acf27bc
dd6d5b6
27ff361
e432e7f
748a7cf
0158959
597464d
949ace2
10887e2
2e7e6c1
2d6b3df
d4255c2
44f70f8
0a3f3d5
462c641
7858e0c
c726069
a45c7dc
8899efb
1052a78
dec727d
0c841c3
5906591
814f420
a2ac6a9
c9ce479
14af049
b6bc24d
f4e1df5
61797d3
b2ba3e7
82d9aef
bf68cfd
7c9ff4b
fae1ff5
06e594e
952dbc0
a243655
cfcf227
90e513c
6e30db9
5db61e4
0cbb660
03647e0
8b3ca01
aef1005
76b1913
719c1f1
fb168b4
4197a91
0b33ebc
98f4cc1
cc327a0
152f279
72b6c59
06d3e72
537f3df
b6749eb
c839e66
7189363
1813db0
047d1b1
c7f4373
e86ea21
32ae7c1
3eba9aa
4141908
bc589c8
f77fd09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,56 +4,81 @@ | |
For more details about this platform, please refer to the documentation at | ||
https://home-assistant.io/components/light.tradfri/ | ||
""" | ||
import asyncio | ||
import logging | ||
|
||
from homeassistant.core import callback | ||
from homeassistant.components.light import ( | ||
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, | ||
SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, Light) | ||
from homeassistant.components.light import ( | ||
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA) | ||
from homeassistant.components.tradfri import ( | ||
KEY_GATEWAY, KEY_TRADFRI_GROUPS, KEY_API) | ||
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION, | ||
SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, SUPPORT_COLOR_TEMP, | ||
SUPPORT_RGB_COLOR, Light) | ||
from homeassistant.components.light import \ | ||
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA | ||
from homeassistant.components.tradfri import KEY_GATEWAY, KEY_TRADFRI_GROUPS, \ | ||
KEY_API | ||
from homeassistant.util import color as color_util | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
DEPENDENCIES = ['tradfri'] | ||
PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA | ||
IKEA = 'IKEA of Sweden' | ||
TRADFRI_LIGHT_MANAGER = 'Tradfri Light Manager' | ||
SUPPORTED_FEATURES = (SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION) | ||
ALLOWED_TEMPERATURES = {IKEA} | ||
|
||
|
||
def setup_platform(hass, config, add_devices, discovery_info=None): | ||
@asyncio.coroutine | ||
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): | ||
"""Set up the IKEA Tradfri Light platform.""" | ||
if discovery_info is None: | ||
return | ||
|
||
gateway_id = discovery_info['gateway'] | ||
api = hass.data[KEY_API][gateway_id] | ||
gateway = hass.data[KEY_GATEWAY][gateway_id] | ||
devices = api(gateway.get_devices()) | ||
lights = [dev for dev in devices if api(dev).has_light_control] | ||
add_devices(Tradfri(light, api) for light in lights) | ||
|
||
devices_command = gateway.get_devices() | ||
devices_commands = yield from api(devices_command) | ||
devices = yield from api(*devices_commands) | ||
lights = [dev for dev in devices if dev.has_light_control] | ||
if lights: | ||
async_add_devices(TradfriLight(light, api) for light in lights) | ||
|
||
allow_tradfri_groups = hass.data[KEY_TRADFRI_GROUPS][gateway_id] | ||
if allow_tradfri_groups: | ||
groups = api(gateway.get_groups()) | ||
add_devices(TradfriGroup(group, api) for group in groups) | ||
groups_command = gateway.get_groups() | ||
groups_commands = yield from api(groups_command) | ||
groups = yield from api(*groups_commands) | ||
if groups: | ||
async_add_devices(TradfriGroup(group, api) for group in groups) | ||
|
||
|
||
class TradfriGroup(Light): | ||
"""The platform class required by hass.""" | ||
|
||
def __init__(self, light, api): | ||
"""Initialize a Group.""" | ||
self._group = api(light) | ||
self._api = api | ||
self._name = self._group.name | ||
self._group = light | ||
self._name = light.name | ||
|
||
self._refresh(light) | ||
|
||
@asyncio.coroutine | ||
def async_added_to_hass(self): | ||
"""Start thread when added to hass.""" | ||
self._async_start_observe() | ||
|
||
@property | ||
def should_poll(self): | ||
"""No polling needed for tradfri group.""" | ||
return False | ||
|
||
@property | ||
def supported_features(self): | ||
"""Flag supported features.""" | ||
return SUPPORT_BRIGHTNESS | ||
return SUPPORTED_FEATURES | ||
|
||
@property | ||
def name(self): | ||
|
@@ -70,49 +95,68 @@ def brightness(self): | |
"""Return the brightness of the group lights.""" | ||
return self._group.dimmer | ||
|
||
def turn_off(self, **kwargs): | ||
@asyncio.coroutine | ||
def async_turn_off(self, **kwargs): | ||
"""Instruct the group lights to turn off.""" | ||
self._api(self._group.set_state(0)) | ||
self.hass.async_add_job(self._api(self._group.set_state(0))) | ||
|
||
def turn_on(self, **kwargs): | ||
@asyncio.coroutine | ||
def async_turn_on(self, **kwargs): | ||
"""Instruct the group lights to turn on, or dim.""" | ||
keys = {} | ||
if ATTR_TRANSITION in kwargs: | ||
keys['transition_time'] = int(kwargs[ATTR_TRANSITION]) | ||
|
||
if ATTR_BRIGHTNESS in kwargs: | ||
self._api(self._group.set_dimmer(kwargs[ATTR_BRIGHTNESS])) | ||
self.hass.async_add_job(self._api( | ||
self._group.set_dimmer(kwargs[ATTR_BRIGHTNESS], **keys))) | ||
else: | ||
self._api(self._group.set_state(1)) | ||
self.hass.async_add_job(self._api(self._group.set_state(1))) | ||
|
||
@callback | ||
def _async_start_observe(self, exc=None): | ||
"""Start observation of light.""" | ||
from pytradfri.error import PyTradFriError | ||
if exc: | ||
_LOGGER.warning("Observation failed for %s", self._name, | ||
exc_info=exc) | ||
|
||
def update(self): | ||
"""Fetch new state data for this group.""" | ||
from pytradfri import RequestTimeout | ||
try: | ||
self._api(self._group.update()) | ||
except RequestTimeout: | ||
_LOGGER.warning("Tradfri update request timed out") | ||
cmd = self._group.observe(callback=self._observe_update, | ||
err_callback=self._async_start_observe, | ||
duration=0) | ||
self.hass.async_add_job(self._api(cmd)) | ||
except PyTradFriError as err: | ||
_LOGGER.warning("Observation failed, trying again", exc_info=err) | ||
self._async_start_observe() | ||
|
||
def _refresh(self, group): | ||
"""Refresh the light data.""" | ||
self._group = group | ||
self._name = group.name | ||
|
||
def _observe_update(self, tradfri_device): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also when a method is an async friendly callback, add an |
||
"""Receive new state data for this light.""" | ||
self._refresh(tradfri_device) | ||
|
||
self.hass.async_add_job(self.async_update_ha_state()) | ||
|
||
|
||
class Tradfri(Light): | ||
"""The platform class required by Home Asisstant.""" | ||
class TradfriLight(Light): | ||
"""The platform class required by Home Assistant.""" | ||
|
||
def __init__(self, light, api): | ||
"""Initialize a Light.""" | ||
self._light = api(light) | ||
self._api = api | ||
|
||
# Caching of LightControl and light object | ||
self._light_control = self._light.light_control | ||
self._light_data = self._light_control.lights[0] | ||
self._name = self._light.name | ||
self._light = None | ||
self._light_control = None | ||
self._light_data = None | ||
self._name = None | ||
self._rgb_color = None | ||
self._features = SUPPORT_BRIGHTNESS | ||
self._features = SUPPORTED_FEATURES | ||
self._temp_supported = False | ||
|
||
if self._light_data.hex_color is not None: | ||
if self._light.device_info.manufacturer == IKEA: | ||
self._features |= SUPPORT_COLOR_TEMP | ||
else: | ||
self._features |= SUPPORT_RGB_COLOR | ||
|
||
self._ok_temps = \ | ||
self._light.device_info.manufacturer in ALLOWED_TEMPERATURES | ||
self._refresh(light) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that should be call inside There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not actually doing a refresh here, just setting instance variables. |
||
|
||
@property | ||
def min_mireds(self): | ||
|
@@ -126,6 +170,30 @@ def max_mireds(self): | |
from pytradfri.color import MIN_KELVIN_WS | ||
return color_util.color_temperature_kelvin_to_mired(MIN_KELVIN_WS) | ||
|
||
@property | ||
def device_state_attributes(self): | ||
"""Return the devices' state attributes.""" | ||
info = self._light.device_info | ||
attrs = { | ||
'manufacturer': info.manufacturer, | ||
'model_number': info.model_number, | ||
'serial': info.serial, | ||
'firmware_version': info.firmware_version, | ||
'power_source': info.power_source_str, | ||
'battery_level': info.battery_level | ||
} | ||
return attrs | ||
|
||
@asyncio.coroutine | ||
def async_added_to_hass(self): | ||
"""Start thread when added to hass.""" | ||
self._async_start_observe() | ||
|
||
@property | ||
def should_poll(self): | ||
"""No polling needed for tradfri light.""" | ||
return False | ||
|
||
@property | ||
def supported_features(self): | ||
"""Flag supported features.""" | ||
|
@@ -151,7 +219,7 @@ def color_temp(self): | |
"""Return the CT color value in mireds.""" | ||
if (self._light_data.kelvin_color is None or | ||
self.supported_features & SUPPORT_COLOR_TEMP == 0 or | ||
not self._ok_temps): | ||
not self._temp_supported): | ||
return None | ||
return color_util.color_temperature_kelvin_to_mired( | ||
self._light_data.kelvin_color | ||
|
@@ -162,42 +230,90 @@ def rgb_color(self): | |
"""RGB color of the light.""" | ||
return self._rgb_color | ||
|
||
def turn_off(self, **kwargs): | ||
@asyncio.coroutine | ||
def async_turn_off(self, **kwargs): | ||
"""Instruct the light to turn off.""" | ||
self._api(self._light_control.set_state(False)) | ||
self.hass.async_add_job(self._api( | ||
self._light_control.set_state(False))) | ||
|
||
def turn_on(self, **kwargs): | ||
@asyncio.coroutine | ||
def async_turn_on(self, **kwargs): | ||
""" | ||
Instruct the light to turn on. | ||
|
||
After adding "self._light_data.hexcolor is not None" | ||
for ATTR_RGB_COLOR, this also supports Philips Hue bulbs. | ||
""" | ||
if ATTR_BRIGHTNESS in kwargs: | ||
self._api(self._light_control.set_dimmer(kwargs[ATTR_BRIGHTNESS])) | ||
else: | ||
self._api(self._light_control.set_state(True)) | ||
|
||
if ATTR_RGB_COLOR in kwargs and self._light_data.hex_color is not None: | ||
self._api(self._light.light_control.set_rgb_color( | ||
*kwargs[ATTR_RGB_COLOR])) | ||
self.hass.async_add_job(self._api( | ||
self._light.light_control.set_rgb_color( | ||
*kwargs[ATTR_RGB_COLOR]))) | ||
|
||
elif ATTR_COLOR_TEMP in kwargs and \ | ||
self._light_data.hex_color is not None and self._ok_temps: | ||
self._light_data.hex_color is not None and \ | ||
self._temp_supported: | ||
kelvin = color_util.color_temperature_mired_to_kelvin( | ||
kwargs[ATTR_COLOR_TEMP]) | ||
self._api(self._light_control.set_kelvin_color(kelvin)) | ||
self.hass.async_add_job(self._api( | ||
self._light_control.set_kelvin_color(kelvin))) | ||
|
||
keys = {} | ||
if ATTR_TRANSITION in kwargs: | ||
keys['transition_time'] = int(kwargs[ATTR_TRANSITION]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. keys['transition_time'] = int(kwargs[ATTR_TRANSITION] * 10) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also it would be nice to set a default 'transition_time' (possibly 1 sec?) This would enable transition effect when brightness is adjusted directly via the slider. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @javefang Personally I don't like the idea of default transition time. But I noticed, after decompiling, that the default transition time in the ikea tradfri android app is 0.5 seconds. Found in: com.ikea.tradfri.lighting.ipso.Light There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I came from the Hue platform, which always have a smooth transition on brightness change. In the end it is probably down to personal preference, so it feels like it would be better if we either keeping it consistent with the app (0.5 sec), or make it configurable? |
||
|
||
if ATTR_BRIGHTNESS in kwargs: | ||
self.hass.async_add_job(self._api( | ||
self._light_control.set_dimmer(kwargs[ATTR_BRIGHTNESS], | ||
**keys))) | ||
else: | ||
self.hass.async_add_job(self._api( | ||
self._light_control.set_state(True))) | ||
|
||
@callback | ||
def _async_start_observe(self, exc=None): | ||
"""Start observation of light.""" | ||
from pytradfri.error import PyTradFriError | ||
if exc: | ||
_LOGGER.warning("Observation failed for %s", self._name, | ||
exc_info=exc) | ||
|
||
def update(self): | ||
"""Fetch new state data for this light.""" | ||
from pytradfri import RequestTimeout | ||
try: | ||
self._api(self._light.update()) | ||
except RequestTimeout as exception: | ||
_LOGGER.warning("Tradfri update request timed out: %s", exception) | ||
cmd = self._light.observe(callback=self._observe_update, | ||
err_callback=self._async_start_observe, | ||
duration=0) | ||
self.hass.async_add_job(self._api(cmd)) | ||
except PyTradFriError as err: | ||
_LOGGER.warning("Observation failed, trying again", exc_info=err) | ||
self._async_start_observe() | ||
|
||
def _refresh(self, light): | ||
"""Refresh the light data.""" | ||
self._light = light | ||
|
||
# Caching of LightControl and light object | ||
self._light_control = light.light_control | ||
self._light_data = light.light_control.lights[0] | ||
self._name = light.name | ||
self._rgb_color = None | ||
self._features = SUPPORTED_FEATURES | ||
|
||
if self._light_data.hex_color is not None: | ||
if self._light.device_info.manufacturer == IKEA: | ||
self._features |= SUPPORT_COLOR_TEMP | ||
else: | ||
self._features |= SUPPORT_RGB_COLOR | ||
|
||
self._temp_supported = self._light.device_info.manufacturer \ | ||
in ALLOWED_TEMPERATURES | ||
|
||
def _observe_update(self, tradfri_device): | ||
"""Receive new state data for this light.""" | ||
self._refresh(tradfri_device) | ||
|
||
# Handle Hue lights paired with the gateway | ||
# hex_color is 0 when bulb is unreachable | ||
if self._light_data.hex_color not in (None, '0'): | ||
self._rgb_color = color_util.rgb_hex_to_rgb_list( | ||
self._light_data.hex_color) | ||
|
||
self.hass.async_add_job(self.async_update_ha_state()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keys['transition_time'] = int(kwargs[ATTR_TRANSITION] * 10)