mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Use pydeconz interface controls for cover platform (#74535)
This commit is contained in:
parent
5e63a44e71
commit
b071affcb4
@ -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,
|
||||||
|
)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user