Skip to content

Commit 59817ee

Browse files
committed
cover apis and tests
1 parent 4583964 commit 59817ee

File tree

11 files changed

+402
-54
lines changed

11 files changed

+402
-54
lines changed

tests/test_cover.py

Lines changed: 198 additions & 40 deletions
Large diffs are not rendered by default.

zha/application/gateway.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import logging
1111
import time
1212
from types import TracebackType
13-
from typing import TYPE_CHECKING, Any, Final, Self, TypeVar, cast
13+
from typing import TYPE_CHECKING, Any, Final, Self, cast
1414

1515
from async_timeout import timeout
1616
import websockets
@@ -116,7 +116,6 @@
116116
from zha.zigbee.model import ExtendedDeviceInfo, ZHAEvent
117117

118118
BLOCK_LOG_TIMEOUT: Final[int] = 60
119-
_R = TypeVar("_R")
120119
_LOGGER = logging.getLogger(__name__)
121120

122121

zha/application/platforms/cover/__init__.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
CoverEntityFeature,
2828
WCAttrs,
2929
)
30-
from zha.application.platforms.cover.model import CoverEntityInfo
30+
from zha.application.platforms.cover.model import CoverEntityInfo, ShadeEntityInfo
3131
from zha.application.registries import PLATFORM_ENTITIES
3232
from zha.exceptions import ZHAException
3333
from zha.zigbee.cluster_handlers.closures import WindowCoveringClusterHandler
@@ -159,6 +159,13 @@ def supported_features(self) -> CoverEntityFeature:
159159
"""Return supported features."""
160160
return self._attr_supported_features
161161

162+
@property
163+
def info_object(self) -> CoverEntityInfo:
164+
"""Return the info object for this entity."""
165+
return CoverEntityInfo(
166+
**super().info_object.__dict__, supported_features=self.supported_features
167+
)
168+
162169
@property
163170
def state(self) -> dict[str, Any]:
164171
"""Get the state of the cover."""
@@ -469,6 +476,13 @@ def __init__(
469476
| CoverEntityFeature.SET_POSITION
470477
)
471478

479+
@property
480+
def info_object(self) -> ShadeEntityInfo:
481+
"""Return the info object for this entity."""
482+
return ShadeEntityInfo(
483+
**super().info_object.__dict__, supported_features=self.supported_features
484+
)
485+
472486
@property
473487
def state(self) -> dict[str, Any]:
474488
"""Get the state of the cover."""
@@ -481,6 +495,8 @@ def state(self) -> dict[str, Any]:
481495
{
482496
ATTR_CURRENT_POSITION: self.current_cover_position,
483497
"is_closed": self.is_closed,
498+
"is_opening": self.is_opening,
499+
"is_closing": self.is_closing,
484500
"state": state,
485501
}
486502
)
@@ -641,24 +657,34 @@ def current_cover_tilt_position(self) -> int | None:
641657

642658
async def async_open_cover(self, **kwargs: Any) -> None:
643659
"""Open the cover."""
660+
await self._device.gateway.covers.open_cover(self.info_object)
644661

645662
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
646663
"""Open the cover tilt."""
664+
await self._device.gateway.covers.open_cover_tilt(self.info_object)
647665

648666
async def async_close_cover(self, **kwargs: Any) -> None:
649667
"""Close the cover."""
668+
await self._device.gateway.covers.close_cover(self.info_object)
650669

651670
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
652671
"""Close the cover tilt."""
672+
await self._device.gateway.covers.close_cover_tilt(self.info_object)
653673

654674
async def async_set_cover_position(self, **kwargs: Any) -> None:
655675
"""Move the cover to a specific position."""
676+
await self._device.gateway.covers.set_cover_position(self.info_object, **kwargs)
656677

657678
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
658679
"""Move the cover tilt to a specific position."""
680+
await self._device.gateway.covers.set_cover_tilt_position(
681+
self.info_object, **kwargs
682+
)
659683

660684
async def async_stop_cover(self, **kwargs: Any) -> None:
661685
"""Stop the cover."""
686+
await self._device.gateway.covers.stop_cover(self.info_object)
662687

663688
async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
664689
"""Stop the cover tilt."""
690+
await self._device.gateway.covers.stop_cover_tilt(self.info_object)

zha/application/platforms/cover/model.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from typing import Literal
66

7+
from zha.application.platforms.cover.const import CoverEntityFeature
78
from zha.application.platforms.model import BasePlatformEntityInfo
89
from zha.model import BaseModel
910

@@ -13,6 +14,8 @@ class CoverState(BaseModel):
1314

1415
class_name: Literal["Cover"] = "Cover"
1516
current_position: int | None = None
17+
target_lift_position: int | None = None
18+
target_tilt_position: int | None = None
1619
state: str | None = None
1720
is_opening: bool
1821
is_closing: bool
@@ -34,11 +37,13 @@ class CoverEntityInfo(BasePlatformEntityInfo):
3437
"""Cover entity model."""
3538

3639
class_name: Literal["Cover"]
40+
supported_features: CoverEntityFeature
3741
state: CoverState
3842

3943

4044
class ShadeEntityInfo(BasePlatformEntityInfo):
4145
"""Shade entity model."""
4246

4347
class_name: Literal["Shade", "KeenVent"]
48+
supported_features: CoverEntityFeature
4449
state: ShadeState

zha/application/platforms/cover/websocket_api.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,24 @@ async def open_cover(server: Server, client: Client, command: CoverOpenCommand)
3131
await execute_platform_entity_command(server, client, command, "async_open_cover")
3232

3333

34+
class CoverOpenTiltCommand(PlatformEntityCommand):
35+
"""Cover open tilt command."""
36+
37+
command: Literal[APICommands.COVER_OPEN_TILT] = APICommands.COVER_OPEN_TILT
38+
platform: str = Platform.COVER
39+
40+
41+
@decorators.websocket_command(CoverOpenTiltCommand)
42+
@decorators.async_response
43+
async def open_cover_tilt(
44+
server: Server, client: Client, command: CoverOpenTiltCommand
45+
) -> None:
46+
"""Open the cover tilt."""
47+
await execute_platform_entity_command(
48+
server, client, command, "async_open_cover_tilt"
49+
)
50+
51+
3452
class CoverCloseCommand(PlatformEntityCommand):
3553
"""Cover close command."""
3654

@@ -47,6 +65,24 @@ async def close_cover(
4765
await execute_platform_entity_command(server, client, command, "async_close_cover")
4866

4967

68+
class CoverCloseTiltCommand(PlatformEntityCommand):
69+
"""Cover close tilt command."""
70+
71+
command: Literal[APICommands.COVER_CLOSE_TILT] = APICommands.COVER_CLOSE_TILT
72+
platform: str = Platform.COVER
73+
74+
75+
@decorators.websocket_command(CoverCloseTiltCommand)
76+
@decorators.async_response
77+
async def close_cover_tilt(
78+
server: Server, client: Client, command: CoverCloseTiltCommand
79+
) -> None:
80+
"""Close the cover tilt."""
81+
await execute_platform_entity_command(
82+
server, client, command, "async_close_cover_tilt"
83+
)
84+
85+
5086
class CoverSetPositionCommand(PlatformEntityCommand):
5187
"""Cover set position command."""
5288

@@ -66,6 +102,27 @@ async def set_position(
66102
)
67103

68104

105+
class CoverSetTiltPositionCommand(PlatformEntityCommand):
106+
"""Cover set position command."""
107+
108+
command: Literal[APICommands.COVER_SET_TILT_POSITION] = (
109+
APICommands.COVER_SET_TILT_POSITION
110+
)
111+
platform: str = Platform.COVER
112+
tilt_position: int
113+
114+
115+
@decorators.websocket_command(CoverSetTiltPositionCommand)
116+
@decorators.async_response
117+
async def set_tilt_position(
118+
server: Server, client: Client, command: CoverSetTiltPositionCommand
119+
) -> None:
120+
"""Set the cover tilt position."""
121+
await execute_platform_entity_command(
122+
server, client, command, "async_set_cover_tilt_position"
123+
)
124+
125+
69126
class CoverStopCommand(PlatformEntityCommand):
70127
"""Cover stop command."""
71128

@@ -80,9 +137,31 @@ async def stop_cover(server: Server, client: Client, command: CoverStopCommand)
80137
await execute_platform_entity_command(server, client, command, "async_stop_cover")
81138

82139

140+
class CoverStopTiltCommand(PlatformEntityCommand):
141+
"""Cover stop tilt command."""
142+
143+
command: Literal[APICommands.COVER_STOP_TILT] = APICommands.COVER_STOP_TILT
144+
platform: str = Platform.COVER
145+
146+
147+
@decorators.websocket_command(CoverStopTiltCommand)
148+
@decorators.async_response
149+
async def stop_cover_tilt(
150+
server: Server, client: Client, command: CoverStopTiltCommand
151+
) -> None:
152+
"""Stop the cover tilt."""
153+
await execute_platform_entity_command(
154+
server, client, command, "async_stop_cover_tilt"
155+
)
156+
157+
83158
def load_api(server: Server) -> None:
84159
"""Load the api command handlers."""
85160
register_api_command(server, open_cover)
86161
register_api_command(server, close_cover)
87162
register_api_command(server, set_position)
88163
register_api_command(server, stop_cover)
164+
register_api_command(server, open_cover_tilt)
165+
register_api_command(server, close_cover_tilt)
166+
register_api_command(server, set_tilt_position)
167+
register_api_command(server, stop_cover_tilt)

zha/application/websocket_api.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
WebSocketCommand,
1818
WriteClusterAttributeResponse,
1919
)
20-
from zha.zigbee.device import Device
21-
from zha.zigbee.group import Group
2220
from zha.zigbee.model import GroupMemberReference
2321

2422
if TYPE_CHECKING:
2523
from zha.application.gateway import WebSocketServerGateway
2624
from zha.websocket.server.client import Client
25+
from zha.zigbee.device import Device
26+
from zha.zigbee.group import Group
27+
2728

2829
GROUP = "group"
2930
MFG_CLUSTER_ID_START = 0xFC00

zha/websocket/client/helpers.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@
2424
)
2525
from zha.application.platforms.cover.websocket_api import (
2626
CoverCloseCommand,
27+
CoverCloseTiltCommand,
2728
CoverOpenCommand,
29+
CoverOpenTiltCommand,
2830
CoverSetPositionCommand,
31+
CoverSetTiltPositionCommand,
2932
CoverStopCommand,
3033
)
3134
from zha.application.platforms.fan.model import FanEntityInfo
@@ -284,6 +287,28 @@ async def close_cover(
284287
)
285288
return await self._client.async_send_command(command)
286289

290+
async def open_cover_tilt(
291+
self, cover_platform_entity: BasePlatformEntityInfo
292+
) -> WebSocketCommandResponse:
293+
"""Open cover tilt."""
294+
ensure_platform_entity(cover_platform_entity, Platform.COVER)
295+
command = CoverOpenTiltCommand(
296+
ieee=cover_platform_entity.device_ieee,
297+
unique_id=cover_platform_entity.unique_id,
298+
)
299+
return await self._client.async_send_command(command)
300+
301+
async def close_cover_tilt(
302+
self, cover_platform_entity: BasePlatformEntityInfo
303+
) -> WebSocketCommandResponse:
304+
"""Open cover tilt."""
305+
ensure_platform_entity(cover_platform_entity, Platform.COVER)
306+
command = CoverCloseTiltCommand(
307+
ieee=cover_platform_entity.device_ieee,
308+
unique_id=cover_platform_entity.unique_id,
309+
)
310+
return await self._client.async_send_command(command)
311+
287312
async def stop_cover(
288313
self, cover_platform_entity: BasePlatformEntityInfo
289314
) -> WebSocketCommandResponse:
@@ -309,6 +334,31 @@ async def set_cover_position(
309334
)
310335
return await self._client.async_send_command(command)
311336

337+
async def set_cover_tilt_position(
338+
self,
339+
cover_platform_entity: BasePlatformEntityInfo,
340+
tilt_position: int,
341+
) -> WebSocketCommandResponse:
342+
"""Set a cover tilt position."""
343+
ensure_platform_entity(cover_platform_entity, Platform.COVER)
344+
command = CoverSetTiltPositionCommand(
345+
ieee=cover_platform_entity.device_ieee,
346+
unique_id=cover_platform_entity.unique_id,
347+
tilt_position=tilt_position,
348+
)
349+
return await self._client.async_send_command(command)
350+
351+
async def stop_cover_tilt(
352+
self, cover_platform_entity: BasePlatformEntityInfo
353+
) -> WebSocketCommandResponse:
354+
"""Stop a cover tilt."""
355+
ensure_platform_entity(cover_platform_entity, Platform.COVER)
356+
command = CoverStopCommand(
357+
ieee=cover_platform_entity.device_ieee,
358+
unique_id=cover_platform_entity.unique_id,
359+
)
360+
return await self._client.async_send_command(command)
361+
312362

313363
class FanHelper:
314364
"""Helper to issue fan commands."""

zha/websocket/const.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,13 @@ class APICommands(StrEnum):
5454
CLIMATE_SET_PRESET_MODE = "climate_set_preset_mode"
5555

5656
COVER_OPEN = "cover_open"
57+
COVER_OPEN_TILT = "cover_open_tilt"
5758
COVER_CLOSE = "cover_close"
59+
COVER_CLOSE_TILT = "cover_close_tilt"
5860
COVER_STOP = "cover_stop"
5961
COVER_SET_POSITION = "cover_set_position"
62+
COVER_SET_TILT_POSITION = "cover_set_tilt_position"
63+
COVER_STOP_TILT = "cover_stop_tilt"
6064

6165
FAN_TURN_ON = "fan_turn_on"
6266
FAN_TURN_OFF = "fan_turn_off"

zha/websocket/server/api/model.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ class WebSocketCommand(BaseModel):
7575
APICommands.COVER_SET_POSITION,
7676
APICommands.COVER_OPEN,
7777
APICommands.COVER_CLOSE,
78+
APICommands.COVER_OPEN_TILT,
79+
APICommands.COVER_CLOSE_TILT,
80+
APICommands.COVER_SET_TILT_POSITION,
81+
APICommands.COVER_STOP_TILT,
7882
APICommands.CLIMATE_SET_TEMPERATURE,
7983
APICommands.CLIMATE_SET_HVAC_MODE,
8084
APICommands.CLIMATE_SET_FAN_MODE,
@@ -118,9 +122,13 @@ class ErrorResponse(WebSocketCommandResponse):
118122
"error.fan_set_percentage",
119123
"error.fan_set_preset_mode",
120124
"error.cover_open",
125+
"error.cover_open_tilt",
121126
"error.cover_close",
127+
"error.cover_close_tilt",
122128
"error.cover_set_position",
129+
"error.cover_set_tilt_position",
123130
"error.cover_stop",
131+
"error.cover_stop_tilt",
124132
"error.climate_set_fan_mode",
125133
"error.climate_set_hvac_mode",
126134
"error.climate_set_preset_mode",
@@ -170,6 +178,10 @@ class DefaultResponse(WebSocketCommandResponse):
170178
"cover_close",
171179
"cover_set_position",
172180
"cover_stop",
181+
"cover_stop_tilt",
182+
"cover_open_tilt",
183+
"cover_close_tilt",
184+
"cover_set_tilt_position",
173185
"climate_set_fan_mode",
174186
"climate_set_hvac_mode",
175187
"climate_set_preset_mode",

0 commit comments

Comments
 (0)