mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 22:27:07 +00:00
Implement Keen vents as zha cover devices (#36080)
* Implement Keen vents as cover devices * Update homeassistant/components/zha/cover.py
This commit is contained in:
parent
9212d1c2dc
commit
fe45935f38
@ -98,7 +98,7 @@ DEVICE_CLASS = {
|
|||||||
zigpy.profiles.zha.DeviceType.DIMMABLE_LIGHT: LIGHT,
|
zigpy.profiles.zha.DeviceType.DIMMABLE_LIGHT: LIGHT,
|
||||||
zigpy.profiles.zha.DeviceType.DIMMABLE_PLUG_IN_UNIT: LIGHT,
|
zigpy.profiles.zha.DeviceType.DIMMABLE_PLUG_IN_UNIT: LIGHT,
|
||||||
zigpy.profiles.zha.DeviceType.EXTENDED_COLOR_LIGHT: LIGHT,
|
zigpy.profiles.zha.DeviceType.EXTENDED_COLOR_LIGHT: LIGHT,
|
||||||
zigpy.profiles.zha.DeviceType.LEVEL_CONTROLLABLE_OUTPUT: LIGHT,
|
zigpy.profiles.zha.DeviceType.LEVEL_CONTROLLABLE_OUTPUT: COVER,
|
||||||
zigpy.profiles.zha.DeviceType.ON_OFF_BALLAST: SWITCH,
|
zigpy.profiles.zha.DeviceType.ON_OFF_BALLAST: SWITCH,
|
||||||
zigpy.profiles.zha.DeviceType.ON_OFF_LIGHT: LIGHT,
|
zigpy.profiles.zha.DeviceType.ON_OFF_LIGHT: LIGHT,
|
||||||
zigpy.profiles.zha.DeviceType.ON_OFF_PLUG_IN_UNIT: SWITCH,
|
zigpy.profiles.zha.DeviceType.ON_OFF_PLUG_IN_UNIT: SWITCH,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Support for ZHA covers."""
|
"""Support for ZHA covers."""
|
||||||
|
import asyncio
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
@ -8,6 +9,7 @@ from zigpy.zcl.foundation import Status
|
|||||||
from homeassistant.components.cover import (
|
from homeassistant.components.cover import (
|
||||||
ATTR_CURRENT_POSITION,
|
ATTR_CURRENT_POSITION,
|
||||||
ATTR_POSITION,
|
ATTR_POSITION,
|
||||||
|
DEVICE_CLASS_DAMPER,
|
||||||
DEVICE_CLASS_SHADE,
|
DEVICE_CLASS_SHADE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
CoverEntity,
|
CoverEntity,
|
||||||
@ -278,3 +280,31 @@ class Shade(ZhaEntity, CoverEntity):
|
|||||||
if not isinstance(res, list) or res[1] != Status.SUCCESS:
|
if not isinstance(res, list) or res[1] != Status.SUCCESS:
|
||||||
self.debug("couldn't stop cover: %s", res)
|
self.debug("couldn't stop cover: %s", res)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@STRICT_MATCH(
|
||||||
|
channel_names={CHANNEL_LEVEL, CHANNEL_ON_OFF}, manufacturers="Keen Home Inc"
|
||||||
|
)
|
||||||
|
class KeenVent(Shade):
|
||||||
|
"""Keen vent cover."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_class(self) -> Optional[str]:
|
||||||
|
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||||
|
return DEVICE_CLASS_DAMPER
|
||||||
|
|
||||||
|
async def async_open_cover(self, **kwargs):
|
||||||
|
"""Open the cover."""
|
||||||
|
position = self._position or 100
|
||||||
|
tasks = [
|
||||||
|
self._level_channel.move_to_level_with_on_off(position * 255 / 100, 1),
|
||||||
|
self._on_off_channel.on(),
|
||||||
|
]
|
||||||
|
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
if any([isinstance(result, Exception) for result in results]):
|
||||||
|
self.debug("couldn't open cover")
|
||||||
|
return
|
||||||
|
|
||||||
|
self._is_open = True
|
||||||
|
self._position = position
|
||||||
|
self.async_write_ha_state()
|
||||||
|
@ -61,6 +61,22 @@ def zigpy_shade_device(zigpy_device_mock):
|
|||||||
return zigpy_device_mock(endpoints)
|
return zigpy_device_mock(endpoints)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def zigpy_keen_vent(zigpy_device_mock):
|
||||||
|
"""Zigpy Keen Vent device."""
|
||||||
|
|
||||||
|
endpoints = {
|
||||||
|
1: {
|
||||||
|
"device_type": 3,
|
||||||
|
"in_clusters": [general.LevelControl.cluster_id, general.OnOff.cluster_id],
|
||||||
|
"out_clusters": [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return zigpy_device_mock(
|
||||||
|
endpoints, manufacturer="Keen Home Inc", model="SV02-612-MP-1.3"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
"homeassistant.components.zha.core.channels.closures.WindowCovering.async_initialize"
|
"homeassistant.components.zha.core.channels.closures.WindowCovering.async_initialize"
|
||||||
)
|
)
|
||||||
@ -306,3 +322,56 @@ async def test_restore_state(hass, zha_device_restored, zigpy_shade_device):
|
|||||||
# test that the cover was created and that it is unavailable
|
# test that the cover was created and that it is unavailable
|
||||||
assert hass.states.get(entity_id).state == STATE_OPEN
|
assert hass.states.get(entity_id).state == STATE_OPEN
|
||||||
assert hass.states.get(entity_id).attributes[ATTR_CURRENT_POSITION] == 50
|
assert hass.states.get(entity_id).attributes[ATTR_CURRENT_POSITION] == 50
|
||||||
|
|
||||||
|
|
||||||
|
async def test_keen_vent(hass, zha_device_joined_restored, zigpy_keen_vent):
|
||||||
|
"""Test keen vent."""
|
||||||
|
|
||||||
|
# load up cover domain
|
||||||
|
zha_device = await zha_device_joined_restored(zigpy_keen_vent)
|
||||||
|
|
||||||
|
cluster_on_off = zigpy_keen_vent.endpoints.get(1).on_off
|
||||||
|
cluster_level = zigpy_keen_vent.endpoints.get(1).level
|
||||||
|
entity_id = await find_entity_id(DOMAIN, zha_device, hass)
|
||||||
|
assert entity_id is not None
|
||||||
|
|
||||||
|
# test that the cover was created and that it is unavailable
|
||||||
|
assert hass.states.get(entity_id).state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
# allow traffic to flow through the gateway and device
|
||||||
|
await async_enable_traffic(hass, [zha_device])
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# test that the state has changed from unavailable to off
|
||||||
|
await send_attributes_report(hass, cluster_on_off, {8: 0, 0: False, 1: 1})
|
||||||
|
assert hass.states.get(entity_id).state == STATE_CLOSED
|
||||||
|
|
||||||
|
# open from UI command fails
|
||||||
|
p1 = patch.object(cluster_on_off, "request", side_effect=asyncio.TimeoutError)
|
||||||
|
p2 = patch.object(cluster_level, "request", AsyncMock(return_value=[4, 0]))
|
||||||
|
|
||||||
|
with p1, p2:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True
|
||||||
|
)
|
||||||
|
assert cluster_on_off.request.call_count == 1
|
||||||
|
assert cluster_on_off.request.call_args[0][0] is False
|
||||||
|
assert cluster_on_off.request.call_args[0][1] == 0x0001
|
||||||
|
assert cluster_level.request.call_count == 1
|
||||||
|
assert hass.states.get(entity_id).state == STATE_CLOSED
|
||||||
|
|
||||||
|
# open from UI command success
|
||||||
|
p1 = patch.object(cluster_on_off, "request", AsyncMock(return_value=[1, 0]))
|
||||||
|
p2 = patch.object(cluster_level, "request", AsyncMock(return_value=[4, 0]))
|
||||||
|
|
||||||
|
with p1, p2:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN, SERVICE_OPEN_COVER, {"entity_id": entity_id}, blocking=True
|
||||||
|
)
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
assert cluster_on_off.request.call_count == 1
|
||||||
|
assert cluster_on_off.request.call_args[0][0] is False
|
||||||
|
assert cluster_on_off.request.call_args[0][1] == 0x0001
|
||||||
|
assert cluster_level.request.call_count == 1
|
||||||
|
assert hass.states.get(entity_id).state == STATE_OPEN
|
||||||
|
assert hass.states.get(entity_id).attributes[ATTR_CURRENT_POSITION] == 100
|
||||||
|
@ -1036,16 +1036,16 @@ DEVICES = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entities": [
|
"entities": [
|
||||||
"light.keen_home_inc_sv02_610_mp_1_3_77665544_level_on_off",
|
"cover.keen_home_inc_sv02_610_mp_1_3_77665544_level_on_off",
|
||||||
"sensor.keen_home_inc_sv02_610_mp_1_3_77665544_power",
|
"sensor.keen_home_inc_sv02_610_mp_1_3_77665544_power",
|
||||||
"sensor.keen_home_inc_sv02_610_mp_1_3_77665544_pressure",
|
"sensor.keen_home_inc_sv02_610_mp_1_3_77665544_pressure",
|
||||||
"sensor.keen_home_inc_sv02_610_mp_1_3_77665544_temperature",
|
"sensor.keen_home_inc_sv02_610_mp_1_3_77665544_temperature",
|
||||||
],
|
],
|
||||||
"entity_map": {
|
"entity_map": {
|
||||||
("light", "00:11:22:33:44:55:66:77-1"): {
|
("cover", "00:11:22:33:44:55:66:77-1"): {
|
||||||
"channels": ["level", "on_off"],
|
"channels": ["level", "on_off"],
|
||||||
"entity_class": "Light",
|
"entity_class": "KeenVent",
|
||||||
"entity_id": "light.keen_home_inc_sv02_610_mp_1_3_77665544_level_on_off",
|
"entity_id": "cover.keen_home_inc_sv02_610_mp_1_3_77665544_level_on_off",
|
||||||
},
|
},
|
||||||
("sensor", "00:11:22:33:44:55:66:77-1-1"): {
|
("sensor", "00:11:22:33:44:55:66:77-1-1"): {
|
||||||
"channels": ["power"],
|
"channels": ["power"],
|
||||||
@ -1094,16 +1094,16 @@ DEVICES = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entities": [
|
"entities": [
|
||||||
"light.keen_home_inc_sv02_612_mp_1_2_77665544_level_on_off",
|
"cover.keen_home_inc_sv02_612_mp_1_2_77665544_level_on_off",
|
||||||
"sensor.keen_home_inc_sv02_612_mp_1_2_77665544_power",
|
"sensor.keen_home_inc_sv02_612_mp_1_2_77665544_power",
|
||||||
"sensor.keen_home_inc_sv02_612_mp_1_2_77665544_pressure",
|
"sensor.keen_home_inc_sv02_612_mp_1_2_77665544_pressure",
|
||||||
"sensor.keen_home_inc_sv02_612_mp_1_2_77665544_temperature",
|
"sensor.keen_home_inc_sv02_612_mp_1_2_77665544_temperature",
|
||||||
],
|
],
|
||||||
"entity_map": {
|
"entity_map": {
|
||||||
("light", "00:11:22:33:44:55:66:77-1"): {
|
("cover", "00:11:22:33:44:55:66:77-1"): {
|
||||||
"channels": ["level", "on_off"],
|
"channels": ["level", "on_off"],
|
||||||
"entity_class": "Light",
|
"entity_class": "KeenVent",
|
||||||
"entity_id": "light.keen_home_inc_sv02_612_mp_1_2_77665544_level_on_off",
|
"entity_id": "cover.keen_home_inc_sv02_612_mp_1_2_77665544_level_on_off",
|
||||||
},
|
},
|
||||||
("sensor", "00:11:22:33:44:55:66:77-1-1"): {
|
("sensor", "00:11:22:33:44:55:66:77-1-1"): {
|
||||||
"channels": ["power"],
|
"channels": ["power"],
|
||||||
@ -1152,16 +1152,16 @@ DEVICES = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entities": [
|
"entities": [
|
||||||
"light.keen_home_inc_sv02_612_mp_1_3_77665544_level_on_off",
|
"cover.keen_home_inc_sv02_612_mp_1_3_77665544_level_on_off",
|
||||||
"sensor.keen_home_inc_sv02_612_mp_1_3_77665544_power",
|
"sensor.keen_home_inc_sv02_612_mp_1_3_77665544_power",
|
||||||
"sensor.keen_home_inc_sv02_612_mp_1_3_77665544_pressure",
|
"sensor.keen_home_inc_sv02_612_mp_1_3_77665544_pressure",
|
||||||
"sensor.keen_home_inc_sv02_612_mp_1_3_77665544_temperature",
|
"sensor.keen_home_inc_sv02_612_mp_1_3_77665544_temperature",
|
||||||
],
|
],
|
||||||
"entity_map": {
|
"entity_map": {
|
||||||
("light", "00:11:22:33:44:55:66:77-1"): {
|
("cover", "00:11:22:33:44:55:66:77-1"): {
|
||||||
"channels": ["level", "on_off"],
|
"channels": ["level", "on_off"],
|
||||||
"entity_class": "Light",
|
"entity_class": "KeenVent",
|
||||||
"entity_id": "light.keen_home_inc_sv02_612_mp_1_3_77665544_level_on_off",
|
"entity_id": "cover.keen_home_inc_sv02_612_mp_1_3_77665544_level_on_off",
|
||||||
},
|
},
|
||||||
("sensor", "00:11:22:33:44:55:66:77-1-1"): {
|
("sensor", "00:11:22:33:44:55:66:77-1-1"): {
|
||||||
"channels": ["power"],
|
"channels": ["power"],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user