Use pydeconz interface controls for cover platform (#74535)

This commit is contained in:
Robert Svensson 2022-07-07 00:31:47 +02:00 committed by GitHub
parent 5e63a44e71
commit b071affcb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 50 additions and 107 deletions

View File

@ -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,
)

View File

@ -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