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 typing import Any, cast
from pydeconz.interfaces.lights import CoverAction
from pydeconz.models.event import EventType from pydeconz.models.event import EventType
from pydeconz.models.light.cover import Cover from pydeconz.models.light.cover import Cover
@ -21,7 +22,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .deconz_device import DeconzDevice from .deconz_device import DeconzDevice
from .gateway import DeconzGateway, get_gateway_from_config_entry from .gateway import DeconzGateway, get_gateway_from_config_entry
DEVICE_CLASS = { DECONZ_TYPE_TO_DEVICE_CLASS = {
"Level controllable output": CoverDeviceClass.DAMPER, "Level controllable output": CoverDeviceClass.DAMPER,
"Window covering controller": CoverDeviceClass.SHADE, "Window covering controller": CoverDeviceClass.SHADE,
"Window covering device": CoverDeviceClass.SHADE, "Window covering device": CoverDeviceClass.SHADE,
@ -40,8 +41,7 @@ async def async_setup_entry(
@callback @callback
def async_add_cover(_: EventType, cover_id: str) -> None: def async_add_cover(_: EventType, cover_id: str) -> None:
"""Add cover from deCONZ.""" """Add cover from deCONZ."""
cover = gateway.api.lights.covers[cover_id] async_add_entities([DeconzCover(cover_id, gateway)])
async_add_entities([DeconzCover(cover, gateway)])
gateway.register_platform_add_device_callback( gateway.register_platform_add_device_callback(
async_add_cover, async_add_cover,
@ -55,9 +55,9 @@ class DeconzCover(DeconzDevice, CoverEntity):
TYPE = DOMAIN TYPE = DOMAIN
_device: Cover _device: Cover
def __init__(self, device: Cover, gateway: DeconzGateway) -> None: def __init__(self, cover_id: str, gateway: DeconzGateway) -> None:
"""Set up cover device.""" """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.OPEN
self._attr_supported_features |= CoverEntityFeature.CLOSE 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.STOP_TILT
self._attr_supported_features |= CoverEntityFeature.SET_TILT_POSITION 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 @property
def current_cover_position(self) -> int: def current_cover_position(self) -> int:
@ -85,19 +85,31 @@ class DeconzCover(DeconzDevice, CoverEntity):
async def async_set_cover_position(self, **kwargs: Any) -> None: async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
position = 100 - cast(int, kwargs[ATTR_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: async def async_open_cover(self, **kwargs: Any) -> None:
"""Open cover.""" """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: async def async_close_cover(self, **kwargs: Any) -> None:
"""Close cover.""" """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: async def async_stop_cover(self, **kwargs: Any) -> None:
"""Stop cover.""" """Stop cover."""
await self._device.stop() await self.gateway.api.lights.covers.set_state(
id=self._device.resource_id,
action=CoverAction.STOP,
)
@property @property
def current_cover_tilt_position(self) -> int | None: 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: async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
"""Tilt the cover to a specific position.""" """Tilt the cover to a specific position."""
position = 100 - cast(int, kwargs[ATTR_TILT_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: async def async_open_cover_tilt(self, **kwargs: Any) -> None:
"""Open cover tilt.""" """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: async def async_close_cover_tilt(self, **kwargs: Any) -> None:
"""Close cover tilt.""" """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: async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
"""Stop cover tilt.""" """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 = { data = {
"lights": { "lights": {
"1": { "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", "name": "Window covering device",
"type": "Window covering device", "type": "Window covering device",
"state": {"lift": 100, "open": False, "reachable": True}, "state": {"lift": 100, "open": False, "reachable": True},
"modelid": "lumi.curtain", "modelid": "lumi.curtain",
"uniqueid": "00:00:00:00:00:00:00:01-00", "uniqueid": "00:00:00:00:00:00:00:01-00",
}, },
"3": { "2": {
"name": "Unsupported cover", "name": "Unsupported cover",
"type": "Not a cover", "type": "Not a cover",
"state": {"reachable": True}, "state": {"reachable": True},
"uniqueid": "00:00:00:00:00:00:00:02-00", "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): with patch.dict(DECONZ_WEB_REQUEST, data):
config_entry = await setup_deconz_integration(hass, aioclient_mock) config_entry = await setup_deconz_integration(hass, aioclient_mock)
assert len(hass.states.async_all()) == 5 assert len(hass.states.async_all()) == 2
assert hass.states.get("cover.level_controllable_cover").state == STATE_OPEN cover = hass.states.get("cover.window_covering_device")
assert hass.states.get("cover.window_covering_device").state == STATE_CLOSED assert cover.state == STATE_CLOSED
assert cover.attributes[ATTR_CURRENT_POSITION] == 0
assert not hass.states.get("cover.unsupported_cover") 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 = { event_changed_light = {
"t": "event", "t": "event",
"e": "changed", "e": "changed",
"r": "lights", "r": "lights",
"id": "1", "id": "1",
"state": {"on": True}, "state": {"lift": 0, "open": True},
} }
await mock_deconz_websocket(data=event_changed_light) await mock_deconz_websocket(data=event_changed_light)
await hass.async_block_till_done() 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 # 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 # 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} 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) await hass.config_entries.async_unload(config_entry.entry_id)
states = hass.states.async_all() states = hass.states.async_all()
assert len(states) == 5 assert len(states) == 2
for state in states: for state in states:
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE