Add support for tilt only covers to HomeKit (#53130)
This commit is contained in:
parent
cb1eab6c24
commit
236738c455
4 changed files with 86 additions and 12 deletions
|
@ -127,7 +127,7 @@ def get_accessory(hass, driver, state, aid, config): # noqa: C901
|
||||||
and features & cover.SUPPORT_SET_POSITION
|
and features & cover.SUPPORT_SET_POSITION
|
||||||
):
|
):
|
||||||
a_type = "Window"
|
a_type = "Window"
|
||||||
elif features & cover.SUPPORT_SET_POSITION:
|
elif features & (cover.SUPPORT_SET_POSITION | cover.SUPPORT_SET_TILT_POSITION):
|
||||||
a_type = "WindowCovering"
|
a_type = "WindowCovering"
|
||||||
elif features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE):
|
elif features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE):
|
||||||
a_type = "WindowCoveringBasic"
|
a_type = "WindowCoveringBasic"
|
||||||
|
|
|
@ -13,6 +13,7 @@ from homeassistant.components.cover import (
|
||||||
ATTR_POSITION,
|
ATTR_POSITION,
|
||||||
ATTR_TILT_POSITION,
|
ATTR_TILT_POSITION,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
SUPPORT_SET_POSITION,
|
||||||
SUPPORT_SET_TILT_POSITION,
|
SUPPORT_SET_TILT_POSITION,
|
||||||
SUPPORT_STOP,
|
SUPPORT_STOP,
|
||||||
)
|
)
|
||||||
|
@ -53,6 +54,8 @@ from .const import (
|
||||||
HK_POSITION_GOING_TO_MAX,
|
HK_POSITION_GOING_TO_MAX,
|
||||||
HK_POSITION_GOING_TO_MIN,
|
HK_POSITION_GOING_TO_MIN,
|
||||||
HK_POSITION_STOPPED,
|
HK_POSITION_STOPPED,
|
||||||
|
PROP_MAX_VALUE,
|
||||||
|
PROP_MIN_VALUE,
|
||||||
SERV_GARAGE_DOOR_OPENER,
|
SERV_GARAGE_DOOR_OPENER,
|
||||||
SERV_WINDOW,
|
SERV_WINDOW,
|
||||||
SERV_WINDOW_COVERING,
|
SERV_WINDOW_COVERING,
|
||||||
|
@ -273,12 +276,24 @@ class OpeningDevice(OpeningDeviceBase, HomeAccessory):
|
||||||
"""Initialize a WindowCovering accessory object."""
|
"""Initialize a WindowCovering accessory object."""
|
||||||
super().__init__(*args, category=category, service=service)
|
super().__init__(*args, category=category, service=service)
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
|
|
||||||
self.char_current_position = self.serv_cover.configure_char(
|
self.char_current_position = self.serv_cover.configure_char(
|
||||||
CHAR_CURRENT_POSITION, value=0
|
CHAR_CURRENT_POSITION, value=0
|
||||||
)
|
)
|
||||||
|
target_args = {"value": 0}
|
||||||
|
if self.features & SUPPORT_SET_POSITION:
|
||||||
|
target_args["setter_callback"] = self.move_cover
|
||||||
|
else:
|
||||||
|
# If its tilt only we lock the position state to 0 (closed)
|
||||||
|
# since CHAR_CURRENT_POSITION/CHAR_TARGET_POSITION are required
|
||||||
|
# by homekit, but really don't exist.
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s does not support setting position, current position will be locked to closed",
|
||||||
|
self.entity_id,
|
||||||
|
)
|
||||||
|
target_args["properties"] = {PROP_MIN_VALUE: 0, PROP_MAX_VALUE: 0}
|
||||||
|
|
||||||
self.char_target_position = self.serv_cover.configure_char(
|
self.char_target_position = self.serv_cover.configure_char(
|
||||||
CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover
|
CHAR_TARGET_POSITION, **target_args
|
||||||
)
|
)
|
||||||
self.char_position_state = self.serv_cover.configure_char(
|
self.char_position_state = self.serv_cover.configure_char(
|
||||||
CHAR_POSITION_STATE, value=HK_POSITION_STOPPED
|
CHAR_POSITION_STATE, value=HK_POSITION_STOPPED
|
||||||
|
|
|
@ -126,14 +126,28 @@ def test_types(type_name, entity_id, state, attrs, config):
|
||||||
"Window",
|
"Window",
|
||||||
"cover.set_position",
|
"cover.set_position",
|
||||||
"open",
|
"open",
|
||||||
{ATTR_DEVICE_CLASS: "window", ATTR_SUPPORTED_FEATURES: 4},
|
{
|
||||||
|
ATTR_DEVICE_CLASS: "window",
|
||||||
|
ATTR_SUPPORTED_FEATURES: cover.SUPPORT_SET_POSITION,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"WindowCovering",
|
||||||
|
"cover.set_position",
|
||||||
|
"open",
|
||||||
|
{ATTR_SUPPORTED_FEATURES: cover.SUPPORT_SET_POSITION},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"WindowCovering",
|
||||||
|
"cover.tilt",
|
||||||
|
"open",
|
||||||
|
{ATTR_SUPPORTED_FEATURES: cover.SUPPORT_SET_TILT_POSITION},
|
||||||
),
|
),
|
||||||
("WindowCovering", "cover.set_position", "open", {ATTR_SUPPORTED_FEATURES: 4}),
|
|
||||||
(
|
(
|
||||||
"WindowCoveringBasic",
|
"WindowCoveringBasic",
|
||||||
"cover.open_window",
|
"cover.open_window",
|
||||||
"open",
|
"open",
|
||||||
{ATTR_SUPPORTED_FEATURES: 3},
|
{ATTR_SUPPORTED_FEATURES: (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE)},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,6 +18,8 @@ from homeassistant.components.homekit.const import (
|
||||||
HK_DOOR_CLOSING,
|
HK_DOOR_CLOSING,
|
||||||
HK_DOOR_OPEN,
|
HK_DOOR_OPEN,
|
||||||
HK_DOOR_OPENING,
|
HK_DOOR_OPENING,
|
||||||
|
PROP_MAX_VALUE,
|
||||||
|
PROP_MIN_VALUE,
|
||||||
)
|
)
|
||||||
from homeassistant.components.homekit.type_covers import (
|
from homeassistant.components.homekit.type_covers import (
|
||||||
GarageDoorOpener,
|
GarageDoorOpener,
|
||||||
|
@ -133,7 +135,9 @@ async def test_windowcovering_set_cover_position(hass, hk_driver, events):
|
||||||
"""Test if accessory and HA are updated accordingly."""
|
"""Test if accessory and HA are updated accordingly."""
|
||||||
entity_id = "cover.window"
|
entity_id = "cover.window"
|
||||||
|
|
||||||
hass.states.async_set(entity_id, None)
|
hass.states.async_set(
|
||||||
|
entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: SUPPORT_SET_POSITION}
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
acc = WindowCovering(hass, hk_driver, "Cover", entity_id, 2, None)
|
acc = WindowCovering(hass, hk_driver, "Cover", entity_id, 2, None)
|
||||||
await acc.run()
|
await acc.run()
|
||||||
|
@ -145,31 +149,51 @@ async def test_windowcovering_set_cover_position(hass, hk_driver, events):
|
||||||
assert acc.char_current_position.value == 0
|
assert acc.char_current_position.value == 0
|
||||||
assert acc.char_target_position.value == 0
|
assert acc.char_target_position.value == 0
|
||||||
|
|
||||||
hass.states.async_set(entity_id, STATE_UNKNOWN, {ATTR_CURRENT_POSITION: None})
|
hass.states.async_set(
|
||||||
|
entity_id,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
{ATTR_SUPPORTED_FEATURES: SUPPORT_SET_POSITION, ATTR_CURRENT_POSITION: None},
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert acc.char_current_position.value == 0
|
assert acc.char_current_position.value == 0
|
||||||
assert acc.char_target_position.value == 0
|
assert acc.char_target_position.value == 0
|
||||||
assert acc.char_position_state.value == 2
|
assert acc.char_position_state.value == 2
|
||||||
|
|
||||||
hass.states.async_set(entity_id, STATE_OPENING, {ATTR_CURRENT_POSITION: 60})
|
hass.states.async_set(
|
||||||
|
entity_id,
|
||||||
|
STATE_OPENING,
|
||||||
|
{ATTR_SUPPORTED_FEATURES: SUPPORT_SET_POSITION, ATTR_CURRENT_POSITION: 60},
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert acc.char_current_position.value == 60
|
assert acc.char_current_position.value == 60
|
||||||
assert acc.char_target_position.value == 60
|
assert acc.char_target_position.value == 60
|
||||||
assert acc.char_position_state.value == 1
|
assert acc.char_position_state.value == 1
|
||||||
|
|
||||||
hass.states.async_set(entity_id, STATE_OPENING, {ATTR_CURRENT_POSITION: 70.0})
|
hass.states.async_set(
|
||||||
|
entity_id,
|
||||||
|
STATE_OPENING,
|
||||||
|
{ATTR_SUPPORTED_FEATURES: SUPPORT_SET_POSITION, ATTR_CURRENT_POSITION: 70.0},
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert acc.char_current_position.value == 70
|
assert acc.char_current_position.value == 70
|
||||||
assert acc.char_target_position.value == 70
|
assert acc.char_target_position.value == 70
|
||||||
assert acc.char_position_state.value == 1
|
assert acc.char_position_state.value == 1
|
||||||
|
|
||||||
hass.states.async_set(entity_id, STATE_CLOSING, {ATTR_CURRENT_POSITION: 50})
|
hass.states.async_set(
|
||||||
|
entity_id,
|
||||||
|
STATE_CLOSING,
|
||||||
|
{ATTR_SUPPORTED_FEATURES: SUPPORT_SET_POSITION, ATTR_CURRENT_POSITION: 50},
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert acc.char_current_position.value == 50
|
assert acc.char_current_position.value == 50
|
||||||
assert acc.char_target_position.value == 50
|
assert acc.char_target_position.value == 50
|
||||||
assert acc.char_position_state.value == 0
|
assert acc.char_position_state.value == 0
|
||||||
|
|
||||||
hass.states.async_set(entity_id, STATE_OPEN, {ATTR_CURRENT_POSITION: 50})
|
hass.states.async_set(
|
||||||
|
entity_id,
|
||||||
|
STATE_OPEN,
|
||||||
|
{ATTR_SUPPORTED_FEATURES: SUPPORT_SET_POSITION, ATTR_CURRENT_POSITION: 50},
|
||||||
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert acc.char_current_position.value == 50
|
assert acc.char_current_position.value == 50
|
||||||
assert acc.char_target_position.value == 50
|
assert acc.char_target_position.value == 50
|
||||||
|
@ -283,6 +307,27 @@ async def test_windowcovering_cover_set_tilt(hass, hk_driver, events):
|
||||||
assert events[-1].data[ATTR_VALUE] == 75
|
assert events[-1].data[ATTR_VALUE] == 75
|
||||||
|
|
||||||
|
|
||||||
|
async def test_windowcovering_tilt_only(hass, hk_driver, events):
|
||||||
|
"""Test we lock the window covering closed when its tilt only."""
|
||||||
|
entity_id = "cover.window"
|
||||||
|
|
||||||
|
hass.states.async_set(
|
||||||
|
entity_id, STATE_UNKNOWN, {ATTR_SUPPORTED_FEATURES: SUPPORT_SET_TILT_POSITION}
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
acc = WindowCovering(hass, hk_driver, "Cover", entity_id, 2, None)
|
||||||
|
await acc.run()
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert acc.aid == 2
|
||||||
|
assert acc.category == 14 # WindowCovering
|
||||||
|
|
||||||
|
assert acc.char_current_position.value == 0
|
||||||
|
assert acc.char_target_position.value == 0
|
||||||
|
assert acc.char_target_position.properties[PROP_MIN_VALUE] == 0
|
||||||
|
assert acc.char_target_position.properties[PROP_MAX_VALUE] == 0
|
||||||
|
|
||||||
|
|
||||||
async def test_windowcovering_open_close(hass, hk_driver, events):
|
async def test_windowcovering_open_close(hass, hk_driver, events):
|
||||||
"""Test if accessory and HA are updated accordingly."""
|
"""Test if accessory and HA are updated accordingly."""
|
||||||
entity_id = "cover.window"
|
entity_id = "cover.window"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue