diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index 9efbeac366f..3e56882f15a 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Any, cast +from pydeconz.interfaces.lights import CoverAction from pydeconz.models.event import EventType from pydeconz.models.light.cover import Cover @@ -21,7 +22,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from .deconz_device import DeconzDevice from .gateway import DeconzGateway, get_gateway_from_config_entry -DEVICE_CLASS = { +DECONZ_TYPE_TO_DEVICE_CLASS = { "Level controllable output": CoverDeviceClass.DAMPER, "Window covering controller": CoverDeviceClass.SHADE, "Window covering device": CoverDeviceClass.SHADE, @@ -40,8 +41,7 @@ async def async_setup_entry( @callback def async_add_cover(_: EventType, cover_id: str) -> None: """Add cover from deCONZ.""" - cover = gateway.api.lights.covers[cover_id] - async_add_entities([DeconzCover(cover, gateway)]) + async_add_entities([DeconzCover(cover_id, gateway)]) gateway.register_platform_add_device_callback( async_add_cover, @@ -55,9 +55,9 @@ class DeconzCover(DeconzDevice, CoverEntity): TYPE = DOMAIN _device: Cover - def __init__(self, device: Cover, gateway: DeconzGateway) -> None: + def __init__(self, cover_id: str, gateway: DeconzGateway) -> None: """Set up cover device.""" - super().__init__(device, gateway) + super().__init__(cover := gateway.api.lights.covers[cover_id], gateway) self._attr_supported_features = CoverEntityFeature.OPEN self._attr_supported_features |= CoverEntityFeature.CLOSE @@ -70,7 +70,7 @@ class DeconzCover(DeconzDevice, CoverEntity): self._attr_supported_features |= CoverEntityFeature.STOP_TILT self._attr_supported_features |= CoverEntityFeature.SET_TILT_POSITION - self._attr_device_class = DEVICE_CLASS.get(self._device.type) + self._attr_device_class = DECONZ_TYPE_TO_DEVICE_CLASS.get(cover.type) @property def current_cover_position(self) -> int: @@ -85,19 +85,31 @@ class DeconzCover(DeconzDevice, CoverEntity): async def async_set_cover_position(self, **kwargs: Any) -> None: """Move the cover to a specific position.""" position = 100 - cast(int, kwargs[ATTR_POSITION]) - await self._device.set_position(lift=position) + await self.gateway.api.lights.covers.set_state( + id=self._device.resource_id, + lift=position, + ) async def async_open_cover(self, **kwargs: Any) -> None: """Open cover.""" - await self._device.open() + await self.gateway.api.lights.covers.set_state( + id=self._device.resource_id, + action=CoverAction.OPEN, + ) async def async_close_cover(self, **kwargs: Any) -> None: """Close cover.""" - await self._device.close() + await self.gateway.api.lights.covers.set_state( + id=self._device.resource_id, + action=CoverAction.CLOSE, + ) async def async_stop_cover(self, **kwargs: Any) -> None: """Stop cover.""" - await self._device.stop() + await self.gateway.api.lights.covers.set_state( + id=self._device.resource_id, + action=CoverAction.STOP, + ) @property def current_cover_tilt_position(self) -> int | None: @@ -109,16 +121,28 @@ class DeconzCover(DeconzDevice, CoverEntity): async def async_set_cover_tilt_position(self, **kwargs: Any) -> None: """Tilt the cover to a specific position.""" position = 100 - cast(int, kwargs[ATTR_TILT_POSITION]) - await self._device.set_position(tilt=position) + await self.gateway.api.lights.covers.set_state( + id=self._device.resource_id, + tilt=position, + ) async def async_open_cover_tilt(self, **kwargs: Any) -> None: """Open cover tilt.""" - await self._device.set_position(tilt=0) + await self.gateway.api.lights.covers.set_state( + id=self._device.resource_id, + tilt=0, + ) async def async_close_cover_tilt(self, **kwargs: Any) -> None: """Close cover tilt.""" - await self._device.set_position(tilt=100) + await self.gateway.api.lights.covers.set_state( + id=self._device.resource_id, + tilt=100, + ) async def async_stop_cover_tilt(self, **kwargs: Any) -> None: """Stop cover tilt.""" - await self._device.stop() + await self.gateway.api.lights.covers.set_state( + id=self._device.resource_id, + action=CoverAction.STOP, + ) diff --git a/tests/components/deconz/test_cover.py b/tests/components/deconz/test_cover.py index c252b00a228..0c37edc221d 100644 --- a/tests/components/deconz/test_cover.py +++ b/tests/components/deconz/test_cover.py @@ -42,68 +42,48 @@ async def test_cover(hass, aioclient_mock, mock_deconz_websocket): data = { "lights": { "1": { - "name": "Level controllable cover", - "type": "Level controllable output", - "state": {"bri": 254, "on": False, "reachable": True}, - "modelid": "Not zigbee spec", - "uniqueid": "00:00:00:00:00:00:00:00-00", - }, - "2": { "name": "Window covering device", "type": "Window covering device", "state": {"lift": 100, "open": False, "reachable": True}, "modelid": "lumi.curtain", "uniqueid": "00:00:00:00:00:00:00:01-00", }, - "3": { + "2": { "name": "Unsupported cover", "type": "Not a cover", "state": {"reachable": True}, "uniqueid": "00:00:00:00:00:00:00:02-00", }, - "4": { - "name": "deconz old brightness cover", - "type": "Level controllable output", - "state": {"bri": 255, "on": False, "reachable": True}, - "modelid": "Not zigbee spec", - "uniqueid": "00:00:00:00:00:00:00:03-00", - }, - "5": { - "name": "Window covering controller", - "type": "Window covering controller", - "state": {"bri": 253, "on": True, "reachable": True}, - "modelid": "Motor controller", - "uniqueid": "00:00:00:00:00:00:00:04-00", - }, } } with patch.dict(DECONZ_WEB_REQUEST, data): config_entry = await setup_deconz_integration(hass, aioclient_mock) - assert len(hass.states.async_all()) == 5 - assert hass.states.get("cover.level_controllable_cover").state == STATE_OPEN - assert hass.states.get("cover.window_covering_device").state == STATE_CLOSED + assert len(hass.states.async_all()) == 2 + cover = hass.states.get("cover.window_covering_device") + assert cover.state == STATE_CLOSED + assert cover.attributes[ATTR_CURRENT_POSITION] == 0 assert not hass.states.get("cover.unsupported_cover") - assert hass.states.get("cover.deconz_old_brightness_cover").state == STATE_OPEN - assert hass.states.get("cover.window_covering_controller").state == STATE_CLOSED - # Event signals cover is closed + # Event signals cover is open event_changed_light = { "t": "event", "e": "changed", "r": "lights", "id": "1", - "state": {"on": True}, + "state": {"lift": 0, "open": True}, } await mock_deconz_websocket(data=event_changed_light) await hass.async_block_till_done() - assert hass.states.get("cover.level_controllable_cover").state == STATE_CLOSED + cover = hass.states.get("cover.window_covering_device") + assert cover.state == STATE_OPEN + assert cover.attributes[ATTR_CURRENT_POSITION] == 100 # Verify service calls for cover - mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/2/state") + mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") # Service open cover @@ -145,71 +125,10 @@ async def test_cover(hass, aioclient_mock, mock_deconz_websocket): ) assert aioclient_mock.mock_calls[4][2] == {"stop": True} - # Verify service calls for legacy cover - - mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state") - - # Service open cover - - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: "cover.level_controllable_cover"}, - blocking=True, - ) - assert aioclient_mock.mock_calls[5][2] == {"on": False} - - # Service close cover - - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: "cover.level_controllable_cover"}, - blocking=True, - ) - assert aioclient_mock.mock_calls[6][2] == {"on": True} - - # Service set cover position - - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_SET_COVER_POSITION, - {ATTR_ENTITY_ID: "cover.level_controllable_cover", ATTR_POSITION: 40}, - blocking=True, - ) - assert aioclient_mock.mock_calls[7][2] == {"bri": 152} - - # Service stop cover movement - - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_STOP_COVER, - {ATTR_ENTITY_ID: "cover.level_controllable_cover"}, - blocking=True, - ) - assert aioclient_mock.mock_calls[8][2] == {"bri_inc": 0} - - # Test that a reported cover position of 255 (deconz-rest-api < 2.05.73) is interpreted correctly. - assert hass.states.get("cover.deconz_old_brightness_cover").state == STATE_OPEN - - event_changed_light = { - "t": "event", - "e": "changed", - "r": "lights", - "id": "4", - "state": {"on": True}, - } - await mock_deconz_websocket(data=event_changed_light) - await hass.async_block_till_done() - - deconz_old_brightness_cover = hass.states.get("cover.deconz_old_brightness_cover") - assert deconz_old_brightness_cover.state == STATE_CLOSED - assert deconz_old_brightness_cover.attributes[ATTR_CURRENT_POSITION] == 0 - await hass.config_entries.async_unload(config_entry.entry_id) states = hass.states.async_all() - assert len(states) == 5 + assert len(states) == 2 for state in states: assert state.state == STATE_UNAVAILABLE