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:
Alexei Chetroi 2020-05-24 11:10:16 -04:00 committed by GitHub
parent 9212d1c2dc
commit fe45935f38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 112 additions and 13 deletions

View file

@ -98,7 +98,7 @@ DEVICE_CLASS = {
zigpy.profiles.zha.DeviceType.DIMMABLE_LIGHT: LIGHT,
zigpy.profiles.zha.DeviceType.DIMMABLE_PLUG_IN_UNIT: 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_LIGHT: LIGHT,
zigpy.profiles.zha.DeviceType.ON_OFF_PLUG_IN_UNIT: SWITCH,

View file

@ -1,4 +1,5 @@
"""Support for ZHA covers."""
import asyncio
import functools
import logging
from typing import List, Optional
@ -8,6 +9,7 @@ from zigpy.zcl.foundation import Status
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION,
ATTR_POSITION,
DEVICE_CLASS_DAMPER,
DEVICE_CLASS_SHADE,
DOMAIN,
CoverEntity,
@ -278,3 +280,31 @@ class Shade(ZhaEntity, CoverEntity):
if not isinstance(res, list) or res[1] != Status.SUCCESS:
self.debug("couldn't stop cover: %s", res)
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()

View file

@ -61,6 +61,22 @@ def zigpy_shade_device(zigpy_device_mock):
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(
"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
assert hass.states.get(entity_id).state == STATE_OPEN
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

View file

@ -1036,16 +1036,16 @@ DEVICES = [
}
},
"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_pressure",
"sensor.keen_home_inc_sv02_610_mp_1_3_77665544_temperature",
],
"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"],
"entity_class": "Light",
"entity_id": "light.keen_home_inc_sv02_610_mp_1_3_77665544_level_on_off",
"entity_class": "KeenVent",
"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"): {
"channels": ["power"],
@ -1094,16 +1094,16 @@ DEVICES = [
}
},
"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_pressure",
"sensor.keen_home_inc_sv02_612_mp_1_2_77665544_temperature",
],
"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"],
"entity_class": "Light",
"entity_id": "light.keen_home_inc_sv02_612_mp_1_2_77665544_level_on_off",
"entity_class": "KeenVent",
"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"): {
"channels": ["power"],
@ -1152,16 +1152,16 @@ DEVICES = [
}
},
"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_pressure",
"sensor.keen_home_inc_sv02_612_mp_1_3_77665544_temperature",
],
"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"],
"entity_class": "Light",
"entity_id": "light.keen_home_inc_sv02_612_mp_1_3_77665544_level_on_off",
"entity_class": "KeenVent",
"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"): {
"channels": ["power"],