From acb94b0b596ce0708d77295932c7e6b104d9c2fe Mon Sep 17 00:00:00 2001 From: Robert Svensson Date: Tue, 24 Nov 2020 21:42:11 +0100 Subject: [PATCH] Add tilt support to deCONZ covers (#43607) --- homeassistant/components/deconz/cover.py | 56 +++++++++--- homeassistant/components/deconz/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/deconz/test_cover.py | 87 ++++++++++++++++++- 5 files changed, 133 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index 2d191b7f7ca..48218cf893a 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -1,12 +1,17 @@ """Support for deCONZ covers.""" from homeassistant.components.cover import ( ATTR_POSITION, + ATTR_TILT_POSITION, DEVICE_CLASS_WINDOW, DOMAIN, SUPPORT_CLOSE, + SUPPORT_CLOSE_TILT, SUPPORT_OPEN, + SUPPORT_OPEN_TILT, SUPPORT_SET_POSITION, + SUPPORT_SET_TILT_POSITION, SUPPORT_STOP, + SUPPORT_STOP_TILT, CoverEntity, ) from homeassistant.core import callback @@ -60,15 +65,16 @@ class DeconzCover(DeconzDevice, CoverEntity): self._features |= SUPPORT_STOP self._features |= SUPPORT_SET_POSITION - @property - def current_cover_position(self): - """Return the current position of the cover.""" - return 100 - self._device.position + if self._device.tilt is not None: + self._features |= SUPPORT_OPEN_TILT + self._features |= SUPPORT_CLOSE_TILT + self._features |= SUPPORT_STOP_TILT + self._features |= SUPPORT_SET_TILT_POSITION @property - def is_closed(self): - """Return if the cover is closed.""" - return not self._device.is_open + def supported_features(self): + """Flag supported features.""" + return self._features @property def device_class(self): @@ -79,14 +85,19 @@ class DeconzCover(DeconzDevice, CoverEntity): return DEVICE_CLASS_WINDOW @property - def supported_features(self): - """Flag supported features.""" - return self._features + def current_cover_position(self): + """Return the current position of the cover.""" + return 100 - self._device.lift + + @property + def is_closed(self): + """Return if the cover is closed.""" + return not self._device.is_open async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" position = 100 - kwargs[ATTR_POSITION] - await self._device.set_position(position) + await self._device.set_position(lift=position) async def async_open_cover(self, **kwargs): """Open cover.""" @@ -99,3 +110,26 @@ class DeconzCover(DeconzDevice, CoverEntity): async def async_stop_cover(self, **kwargs): """Stop cover.""" await self._device.stop() + + @property + def current_cover_tilt_position(self): + """Return the current tilt position of the cover.""" + if self._device.tilt is not None: + return 100 - self._device.tilt + + async def async_set_cover_tilt_position(self, **kwargs): + """Tilt the cover to a specific position.""" + position = 100 - kwargs[ATTR_TILT_POSITION] + await self._device.set_position(tilt=position) + + async def async_open_cover_tilt(self, **kwargs): + """Open cover tilt.""" + await self._device.set_position(tilt=0) + + async def async_close_cover_tilt(self, **kwargs): + """Close cover tilt.""" + await self._device.set_position(tilt=100) + + async def async_stop_cover_tilt(self, **kwargs): + """Stop cover tilt.""" + await self._device.stop() diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index 7b8abe82472..e9b388e29fe 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -3,7 +3,7 @@ "name": "deCONZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", - "requirements": ["pydeconz==74"], + "requirements": ["pydeconz==75"], "ssdp": [ { "manufacturer": "Royal Philips Electronics" diff --git a/requirements_all.txt b/requirements_all.txt index c85630f7c50..5e2d832f247 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1337,7 +1337,7 @@ pydaikin==2.3.1 pydanfossair==0.1.0 # homeassistant.components.deconz -pydeconz==74 +pydeconz==75 # homeassistant.components.delijn pydelijn==0.6.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a7bc7f10ab2..826229436af 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -670,7 +670,7 @@ pycountry==19.8.18 pydaikin==2.3.1 # homeassistant.components.deconz -pydeconz==74 +pydeconz==75 # homeassistant.components.dexcom pydexcom==0.2.0 diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py index 089a3d90a43..374d3683a6e 100644 --- a/tests/components/deconz/test_cover.py +++ b/tests/components/deconz/test_cover.py @@ -3,12 +3,18 @@ from copy import deepcopy from homeassistant.components.cover import ( + ATTR_CURRENT_TILT_POSITION, ATTR_POSITION, + ATTR_TILT_POSITION, DOMAIN as COVER_DOMAIN, SERVICE_CLOSE_COVER, + SERVICE_CLOSE_COVER_TILT, SERVICE_OPEN_COVER, + SERVICE_OPEN_COVER_TILT, SERVICE_SET_COVER_POSITION, + SERVICE_SET_COVER_TILT_POSITION, SERVICE_STOP_COVER, + SERVICE_STOP_COVER_TILT, ) from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN from homeassistant.components.deconz.gateway import get_gateway_from_config_entry @@ -47,7 +53,7 @@ COVERS = { "id": "deconz old brightness cover id", "name": "deconz old brightness cover", "type": "Level controllable output", - "state": {"bri": 254, "on": False, "reachable": True}, + "state": {"bri": 255, "on": False, "reachable": True}, "modelid": "Not zigbee spec", "uniqueid": "00:00:00:00:00:00:00:03-00", }, @@ -165,7 +171,7 @@ async def test_cover(hass): blocking=True, ) await hass.async_block_till_done() - set_callback.assert_called_with("put", "/lights/2/state", json={"bri_inc": 0}) + set_callback.assert_called_with("put", "/lights/2/state", json={"stop": True}) # Verify service calls for legacy cover @@ -247,3 +253,80 @@ async def test_cover(hass): await hass.config_entries.async_unload(config_entry.entry_id) assert len(hass.states.async_all()) == 0 + + +async def test_tilt_cover(hass): + """Test that tilting a cover works.""" + data = deepcopy(DECONZ_WEB_REQUEST) + data["lights"] = { + "0": { + "etag": "87269755b9b3a046485fdae8d96b252c", + "lastannounced": None, + "lastseen": "2020-08-01T16:22:05Z", + "manufacturername": "AXIS", + "modelid": "Gear", + "name": "Covering device", + "state": { + "bri": 0, + "lift": 0, + "on": False, + "open": True, + "reachable": True, + "tilt": 0, + }, + "swversion": "100-5.3.5.1122", + "type": "Window covering device", + "uniqueid": "00:24:46:00:00:12:34:56-01", + } + } + config_entry = await setup_deconz_integration(hass, get_state_response=data) + gateway = get_gateway_from_config_entry(hass, config_entry) + + assert len(hass.states.async_all()) == 1 + entity = hass.states.get("cover.covering_device") + assert entity.state == STATE_OPEN + assert entity.attributes[ATTR_CURRENT_TILT_POSITION] == 100 + + covering_device = gateway.api.lights["0"] + + with patch.object(covering_device, "_request", return_value=True) as set_callback: + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_SET_COVER_TILT_POSITION, + {ATTR_ENTITY_ID: "cover.covering_device", ATTR_TILT_POSITION: 40}, + blocking=True, + ) + await hass.async_block_till_done() + set_callback.assert_called_with("put", "/lights/0/state", json={"tilt": 60}) + + with patch.object(covering_device, "_request", return_value=True) as set_callback: + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_OPEN_COVER_TILT, + {ATTR_ENTITY_ID: "cover.covering_device"}, + blocking=True, + ) + await hass.async_block_till_done() + set_callback.assert_called_with("put", "/lights/0/state", json={"tilt": 0}) + + with patch.object(covering_device, "_request", return_value=True) as set_callback: + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_CLOSE_COVER_TILT, + {ATTR_ENTITY_ID: "cover.covering_device"}, + blocking=True, + ) + await hass.async_block_till_done() + set_callback.assert_called_with("put", "/lights/0/state", json={"tilt": 100}) + + # Service stop cover movement + + with patch.object(covering_device, "_request", return_value=True) as set_callback: + await hass.services.async_call( + COVER_DOMAIN, + SERVICE_STOP_COVER_TILT, + {ATTR_ENTITY_ID: "cover.covering_device"}, + blocking=True, + ) + await hass.async_block_till_done() + set_callback.assert_called_with("put", "/lights/0/state", json={"stop": True})