From d999df6ab9185107f476d5de900ee9872995337f Mon Sep 17 00:00:00 2001 From: Patrik <21142447+ggravlingen@users.noreply.github.com> Date: Sun, 15 Sep 2019 11:55:37 +0200 Subject: [PATCH 01/10] Add basic support for IKEA Fyrtur blinds --- homeassistant/components/tradfri/cover.py | 139 ++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 homeassistant/components/tradfri/cover.py diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py new file mode 100644 index 00000000000000..f5ab5111b09eea --- /dev/null +++ b/homeassistant/components/tradfri/cover.py @@ -0,0 +1,139 @@ +"""Support for IKEA Tradfri covers.""" +import logging + +from homeassistant.components.cover import CoverDevice, ATTR_POSITION +from homeassistant.core import callback +from . import DOMAIN as TRADFRI_DOMAIN, KEY_API, KEY_GATEWAY +from .const import CONF_GATEWAY_ID + +_LOGGER = logging.getLogger(__name__) + +IKEA = "IKEA of Sweden" + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Load Tradfri covers based on a config entry.""" + gateway_id = config_entry.data[CONF_GATEWAY_ID] + api = hass.data[KEY_API][config_entry.entry_id] + gateway = hass.data[KEY_GATEWAY][config_entry.entry_id] + + devices_commands = await api(gateway.get_devices()) + devices = await api(devices_commands) + covers = [dev for dev in devices if dev.has_blink_control] + if covers: + async_add_entities( + TraddfriCover(cover, api, gateway_id) for cover in covers + ) + + +class TraddfriCover(CoverDevice): + """The platform class required by Home Assistant.""" + + def __init__(self, cover, api, gateway_id): + """Initialize a cover.""" + self._api = api + self._unique_id = f"{gateway_id}-{cover.id}" + self._cover = None + self._switch_control = None + self._cover_data = None + self._name = None + self._available = True + self._gateway_id = gateway_id + + self._refresh(cover) + + @property + def unique_id(self): + """Return unique ID for cover.""" + return self._unique_id + + @property + def device_info(self): + """Return the device info.""" + info = self._cover.device_info + + return { + "identifiers": {(TRADFRI_DOMAIN, self._cover.id)}, + "name": self._name, + "manufacturer": info.manufacturer, + "model": info.model_number, + "sw_version": info.firmware_version, + "via_device": (TRADFRI_DOMAIN, self._gateway_id), + } + + async def async_added_to_hass(self): + """Start thread when added to hass.""" + self._async_start_observe() + + @property + def available(self): + """Return True if entity is available.""" + return self._available + + @property + def should_poll(self): + """No polling needed for tradfri cover.""" + return False + + @property + def name(self): + """Return the display name of this cover.""" + return self._name + + @property + def current_cover_position(self): + """Return current position of cover. + + None is unknown, 0 is closed, 100 is fully open. + """ + return self._cover_data.current_cover_position + + def set_cover_position(self, **kwargs): + """Move the cover to a specific position.""" + if ATTR_POSITION in kwargs: + self._cover_control.set_state(kwargs[ATTR_POSITION]) + + def open_cover(self, **kwargs): + """Open the cover.""" + self._cover_control.set_state(100) + + def close_cover(self, **kwargs): + """Close cover.""" + self._cover_control.set_state(0) + + @callback + def _async_start_observe(self, exc=None): + """Start observation of cover.""" + from pytradfri.error import PytradfriError + + if exc: + self._available = False + self.async_schedule_update_ha_state() + _LOGGER.warning("Observation failed for %s", self._name, exc_info=exc) + + try: + cmd = self._cover.observe( + callback=self._observe_update, + err_callback=self._async_start_observe, + duration=0, + ) + self.hass.async_create_task(self._api(cmd)) + except PytradfriError as err: + _LOGGER.warning("Observation failed, trying again", exc_info=err) + self._async_start_observe() + + def _refresh(self, cover): + """Refresh the cover data.""" + self._cover = cover + + # Caching of BlindControl and cover object + self._available = cover.reachable + self._cover_control = cover.blind_control + self._cover_data = cover.blind_control.blinds[0] + self._name = cover.name + + @callback + def _observe_update(self, tradfri_device): + """Receive new state data for this cover.""" + self._refresh(tradfri_device) + self.async_schedule_update_ha_state() From 145ac1fe90ec9218719f1193414225db596a8a24 Mon Sep 17 00:00:00 2001 From: Patrik <21142447+ggravlingen@users.noreply.github.com> Date: Sun, 15 Sep 2019 12:00:47 +0200 Subject: [PATCH 02/10] Update coveragerc --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index a29586c7b6e1cd..302ff9465549e2 100644 --- a/.coveragerc +++ b/.coveragerc @@ -669,6 +669,7 @@ omit = homeassistant/components/trackr/device_tracker.py homeassistant/components/tradfri/* homeassistant/components/tradfri/light.py + homeassistant/components/tradfri/cover.py homeassistant/components/trafikverket_train/sensor.py homeassistant/components/trafikverket_weatherstation/sensor.py homeassistant/components/transmission/* From 432c4d1d78564024251988bbdaaea0a353300bd6 Mon Sep 17 00:00:00 2001 From: Patrik <21142447+ggravlingen@users.noreply.github.com> Date: Sun, 15 Sep 2019 12:03:04 +0200 Subject: [PATCH 03/10] Fix typo --- homeassistant/components/tradfri/cover.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index f5ab5111b09eea..da775647fa3321 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -19,7 +19,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): devices_commands = await api(gateway.get_devices()) devices = await api(devices_commands) - covers = [dev for dev in devices if dev.has_blink_control] + covers = [dev for dev in devices if dev.has_blind_control] if covers: async_add_entities( TraddfriCover(cover, api, gateway_id) for cover in covers From bade109b6a8869cee9ab0f90230b3ee0206a907f Mon Sep 17 00:00:00 2001 From: Patrik <21142447+ggravlingen@users.noreply.github.com> Date: Sun, 15 Sep 2019 12:07:11 +0200 Subject: [PATCH 04/10] Fix typos --- homeassistant/components/tradfri/cover.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index da775647fa3321..d9185ee13f28bf 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -22,11 +22,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities): covers = [dev for dev in devices if dev.has_blind_control] if covers: async_add_entities( - TraddfriCover(cover, api, gateway_id) for cover in covers + TradfriCover(cover, api, gateway_id) for cover in covers ) -class TraddfriCover(CoverDevice): +class TradfriCover(CoverDevice): """The platform class required by Home Assistant.""" def __init__(self, cover, api, gateway_id): @@ -34,7 +34,7 @@ def __init__(self, cover, api, gateway_id): self._api = api self._unique_id = f"{gateway_id}-{cover.id}" self._cover = None - self._switch_control = None + self._cover_control = None self._cover_data = None self._name = None self._available = True From 1e63786c253f67278034f96e33f630b5fa754415 Mon Sep 17 00:00:00 2001 From: Patrik <21142447+ggravlingen@users.noreply.github.com> Date: Sun, 15 Sep 2019 12:34:57 +0200 Subject: [PATCH 05/10] Update following review --- homeassistant/components/tradfri/cover.py | 9 +++------ homeassistant/components/tradfri/light.py | 9 +++------ homeassistant/components/tradfri/sensor.py | 8 +++----- homeassistant/components/tradfri/switch.py | 6 ++---- 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index d9185ee13f28bf..96a4839fabb5fa 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -1,6 +1,8 @@ """Support for IKEA Tradfri covers.""" import logging +from pytradfri.error import PytradfriError + from homeassistant.components.cover import CoverDevice, ATTR_POSITION from homeassistant.core import callback from . import DOMAIN as TRADFRI_DOMAIN, KEY_API, KEY_GATEWAY @@ -8,8 +10,6 @@ _LOGGER = logging.getLogger(__name__) -IKEA = "IKEA of Sweden" - async def async_setup_entry(hass, config_entry, async_add_entities): """Load Tradfri covers based on a config entry.""" @@ -90,8 +90,7 @@ def current_cover_position(self): def set_cover_position(self, **kwargs): """Move the cover to a specific position.""" - if ATTR_POSITION in kwargs: - self._cover_control.set_state(kwargs[ATTR_POSITION]) + self._cover_control.set_state(kwargs[ATTR_POSITION]) def open_cover(self, **kwargs): """Open the cover.""" @@ -104,8 +103,6 @@ def close_cover(self, **kwargs): @callback def _async_start_observe(self, exc=None): """Start observation of cover.""" - from pytradfri.error import PytradfriError - if exc: self._available = False self.async_schedule_update_ha_state() diff --git a/homeassistant/components/tradfri/light.py b/homeassistant/components/tradfri/light.py index 97fdfd9d36d885..55308d5e039201 100644 --- a/homeassistant/components/tradfri/light.py +++ b/homeassistant/components/tradfri/light.py @@ -1,6 +1,9 @@ """Support for IKEA Tradfri lights.""" import logging +from pytradfri.error import PytradfriError + +import homeassistant.util.color as color_util from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, @@ -14,8 +17,6 @@ Light, ) from homeassistant.core import callback -import homeassistant.util.color as color_util - from . import DOMAIN as TRADFRI_DOMAIN, KEY_API, KEY_GATEWAY from .const import CONF_GATEWAY_ID, CONF_IMPORT_GROUPS @@ -26,7 +27,6 @@ ATTR_SAT = "saturation" ATTR_TRANSITION_TIME = "transition_time" PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA -IKEA = "IKEA of Sweden" TRADFRI_LIGHT_MANAGER = "Tradfri Light Manager" SUPPORTED_FEATURES = SUPPORT_TRANSITION SUPPORTED_GROUP_FEATURES = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION @@ -113,9 +113,6 @@ async def async_turn_on(self, **kwargs): @callback def _async_start_observe(self, exc=None): """Start observation of light.""" - # pylint: disable=import-error - from pytradfri.error import PytradfriError - if exc: _LOGGER.warning("Observation failed for %s", self._name, exc_info=exc) diff --git a/homeassistant/components/tradfri/sensor.py b/homeassistant/components/tradfri/sensor.py index 627a98821549ce..4877dbbb541f1a 100644 --- a/homeassistant/components/tradfri/sensor.py +++ b/homeassistant/components/tradfri/sensor.py @@ -1,10 +1,11 @@ """Support for IKEA Tradfri sensors.""" -from datetime import timedelta import logging +from datetime import timedelta + +from pytradfri.error import PytradfriError from homeassistant.core import callback from homeassistant.helpers.entity import Entity - from . import KEY_API, KEY_GATEWAY _LOGGER = logging.getLogger(__name__) @@ -79,9 +80,6 @@ def state(self): @callback def _async_start_observe(self, exc=None): """Start observation of light.""" - # pylint: disable=import-error - from pytradfri.error import PytradfriError - if exc: _LOGGER.warning("Observation failed for %s", self._name, exc_info=exc) diff --git a/homeassistant/components/tradfri/switch.py b/homeassistant/components/tradfri/switch.py index 4be72eb7359e64..545c1ad93cec17 100644 --- a/homeassistant/components/tradfri/switch.py +++ b/homeassistant/components/tradfri/switch.py @@ -1,15 +1,15 @@ """Support for IKEA Tradfri switches.""" import logging +from pytradfri.error import PytradfriError + from homeassistant.components.switch import SwitchDevice from homeassistant.core import callback - from . import DOMAIN as TRADFRI_DOMAIN, KEY_API, KEY_GATEWAY from .const import CONF_GATEWAY_ID _LOGGER = logging.getLogger(__name__) -IKEA = "IKEA of Sweden" TRADFRI_SWITCH_MANAGER = "Tradfri Switch Manager" @@ -98,8 +98,6 @@ async def async_turn_on(self, **kwargs): @callback def _async_start_observe(self, exc=None): """Start observation of switch.""" - from pytradfri.error import PytradfriError - if exc: self._available = False self.async_schedule_update_ha_state() From 61bc89019d34a3623842cca6aec15ba0894f3ceb Mon Sep 17 00:00:00 2001 From: Patrik <21142447+ggravlingen@users.noreply.github.com> Date: Fri, 20 Sep 2019 19:01:46 +0200 Subject: [PATCH 06/10] Fix incorrect rebase --- homeassistant/components/tradfri/cover.py | 37 +++++++++++++++-------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index 96a4839fabb5fa..3dea978044fcae 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -3,7 +3,13 @@ from pytradfri.error import PytradfriError -from homeassistant.components.cover import CoverDevice, ATTR_POSITION +from homeassistant.components.cover import ( + CoverDevice, + ATTR_POSITION, + SUPPORT_OPEN, + SUPPORT_CLOSE, + SUPPORT_SET_POSITION, +) from homeassistant.core import callback from . import DOMAIN as TRADFRI_DOMAIN, KEY_API, KEY_GATEWAY from .const import CONF_GATEWAY_ID @@ -21,9 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): devices = await api(devices_commands) covers = [dev for dev in devices if dev.has_blind_control] if covers: - async_add_entities( - TradfriCover(cover, api, gateway_id) for cover in covers - ) + async_add_entities(TradfriCover(cover, api, gateway_id) for cover in covers) class TradfriCover(CoverDevice): @@ -42,6 +46,11 @@ def __init__(self, cover, api, gateway_id): self._refresh(cover) + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION + @property def unique_id(self): """Return unique ID for cover.""" @@ -86,19 +95,24 @@ def current_cover_position(self): None is unknown, 0 is closed, 100 is fully open. """ - return self._cover_data.current_cover_position + return 100 - self._cover_data.current_cover_position - def set_cover_position(self, **kwargs): + async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" - self._cover_control.set_state(kwargs[ATTR_POSITION]) + await self._api(self._cover_control.set_state(100 - kwargs[ATTR_POSITION])) - def open_cover(self, **kwargs): + async def async_open_cover(self, **kwargs): """Open the cover.""" - self._cover_control.set_state(100) + await self._api(self._cover_control.set_state(0)) - def close_cover(self, **kwargs): + async def async_close_cover(self, **kwargs): """Close cover.""" - self._cover_control.set_state(0) + await self._api(self._cover_control.set_state(100)) + + @property + def is_closed(self): + """Return if the cover is closed or not.""" + return self.current_cover_position == 0 @callback def _async_start_observe(self, exc=None): @@ -107,7 +121,6 @@ def _async_start_observe(self, exc=None): self._available = False self.async_schedule_update_ha_state() _LOGGER.warning("Observation failed for %s", self._name, exc_info=exc) - try: cmd = self._cover.observe( callback=self._observe_update, From 66fa5295d745cb485021955bf7621779e43fc624 Mon Sep 17 00:00:00 2001 From: Patrik <21142447+ggravlingen@users.noreply.github.com> Date: Fri, 20 Sep 2019 21:23:11 +0200 Subject: [PATCH 07/10] Fix error --- homeassistant/components/tradfri/light.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/tradfri/light.py b/homeassistant/components/tradfri/light.py index 55308d5e039201..615899a98c8d22 100644 --- a/homeassistant/components/tradfri/light.py +++ b/homeassistant/components/tradfri/light.py @@ -336,8 +336,6 @@ async def async_turn_on(self, **kwargs): @callback def _async_start_observe(self, exc=None): """Start observation of light.""" - # pylint: disable=import-error - from pytradfri.error import PytradfriError if exc: self._available = False From 32bb10459dffa10f18c85cfe1513b9339af5a701 Mon Sep 17 00:00:00 2001 From: Patrik <21142447+ggravlingen@users.noreply.github.com> Date: Sat, 21 Sep 2019 17:29:36 +0200 Subject: [PATCH 08/10] Update to new format of unique id --- homeassistant/components/tradfri/cover.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index 3dea978044fcae..e7a70eaa5d2cab 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -36,7 +36,7 @@ class TradfriCover(CoverDevice): def __init__(self, cover, api, gateway_id): """Initialize a cover.""" self._api = api - self._unique_id = f"{gateway_id}-{cover.id}" + self._unique_id = f"cover-{gateway_id}-{cover.id}" self._cover = None self._cover_control = None self._cover_data = None From a05d661702dd896d4f922967bd698cf4d5b38e8a Mon Sep 17 00:00:00 2001 From: Patrik <21142447+ggravlingen@users.noreply.github.com> Date: Sun, 22 Sep 2019 12:00:58 +0200 Subject: [PATCH 09/10] Add cover --- homeassistant/components/tradfri/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py index 87b073db052001..bca91134bedf47 100644 --- a/homeassistant/components/tradfri/__init__.py +++ b/homeassistant/components/tradfri/__init__.py @@ -131,6 +131,9 @@ async def on_hass_stop(event): sw_version=gateway_info.firmware_version, ) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "cover") + ) hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, "light") ) From ae4bbe43ebb45c3851bfab034de42f4583d852a1 Mon Sep 17 00:00:00 2001 From: Patrik <21142447+ggravlingen@users.noreply.github.com> Date: Sun, 22 Sep 2019 22:04:53 +0200 Subject: [PATCH 10/10] Remove reference to cover in unique id --- homeassistant/components/tradfri/cover.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index e7a70eaa5d2cab..3dea978044fcae 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -36,7 +36,7 @@ class TradfriCover(CoverDevice): def __init__(self, cover, api, gateway_id): """Initialize a cover.""" self._api = api - self._unique_id = f"cover-{gateway_id}-{cover.id}" + self._unique_id = f"{gateway_id}-{cover.id}" self._cover = None self._cover_control = None self._cover_data = None