Support basic covers with open/close/stop services HomeKit (#13819)
* Support basic covers with open/close/stop services * Support optional stop * Testspull/13798/merge
parent
23b97b9105
commit
b589dbf26c
|
@ -101,6 +101,8 @@ def get_accessory(hass, state, aid, config):
|
|||
a_type = 'GarageDoorOpener'
|
||||
elif features & SUPPORT_SET_POSITION:
|
||||
a_type = 'WindowCovering'
|
||||
elif features & (SUPPORT_OPEN | SUPPORT_CLOSE):
|
||||
a_type = 'WindowCoveringBasic'
|
||||
|
||||
elif state.domain == 'light':
|
||||
a_type = 'Light'
|
||||
|
|
|
@ -52,7 +52,8 @@ SERV_SMOKE_SENSOR = 'SmokeSensor'
|
|||
SERV_SWITCH = 'Switch'
|
||||
SERV_TEMPERATURE_SENSOR = 'TemperatureSensor'
|
||||
SERV_THERMOSTAT = 'Thermostat'
|
||||
SERV_WINDOW_COVERING = 'WindowCovering' # CurrentPosition, TargetPosition
|
||||
SERV_WINDOW_COVERING = 'WindowCovering'
|
||||
# CurrentPosition, TargetPosition, PositionState
|
||||
|
||||
|
||||
# #### Characteristics ####
|
||||
|
@ -85,6 +86,7 @@ CHAR_MOTION_DETECTED = 'MotionDetected'
|
|||
CHAR_NAME = 'Name'
|
||||
CHAR_OCCUPANCY_DETECTED = 'OccupancyDetected'
|
||||
CHAR_ON = 'On' # boolean
|
||||
CHAR_POSITION_STATE = 'PositionState'
|
||||
CHAR_SATURATION = 'Saturation' # percent
|
||||
CHAR_SERIAL_NUMBER = 'SerialNumber'
|
||||
CHAR_SMOKE_DETECTED = 'SmokeDetected'
|
||||
|
|
|
@ -2,15 +2,17 @@
|
|||
import logging
|
||||
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_CURRENT_POSITION, ATTR_POSITION, DOMAIN)
|
||||
ATTR_CURRENT_POSITION, ATTR_POSITION, DOMAIN, SUPPORT_STOP)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_SET_COVER_POSITION, STATE_OPEN, STATE_CLOSED)
|
||||
ATTR_ENTITY_ID, SERVICE_SET_COVER_POSITION, STATE_OPEN, STATE_CLOSED,
|
||||
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_STOP_COVER,
|
||||
ATTR_SUPPORTED_FEATURES)
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory, add_preload_service, setup_char
|
||||
from .const import (
|
||||
CATEGORY_WINDOW_COVERING, SERV_WINDOW_COVERING,
|
||||
CHAR_CURRENT_POSITION, CHAR_TARGET_POSITION,
|
||||
CHAR_CURRENT_POSITION, CHAR_TARGET_POSITION, CHAR_POSITION_STATE,
|
||||
CATEGORY_GARAGE_DOOR_OPENER, SERV_GARAGE_DOOR_OPENER,
|
||||
CHAR_CURRENT_DOOR_STATE, CHAR_TARGET_DOOR_STATE)
|
||||
|
||||
|
@ -96,3 +98,62 @@ class WindowCovering(HomeAccessory):
|
|||
abs(current_position - self.homekit_target) < 6:
|
||||
self.char_target_position.set_value(current_position)
|
||||
self.homekit_target = None
|
||||
|
||||
|
||||
@TYPES.register('WindowCoveringBasic')
|
||||
class WindowCoveringBasic(HomeAccessory):
|
||||
"""Generate a Window accessory for a cover entity.
|
||||
|
||||
The cover entity must support: open_cover, close_cover,
|
||||
stop_cover (optional).
|
||||
"""
|
||||
|
||||
def __init__(self, *args, config):
|
||||
"""Initialize a WindowCovering accessory object."""
|
||||
super().__init__(*args, category=CATEGORY_WINDOW_COVERING)
|
||||
features = self.hass.states.get(self.entity_id) \
|
||||
.attributes.get(ATTR_SUPPORTED_FEATURES)
|
||||
self.supports_stop = features & SUPPORT_STOP
|
||||
|
||||
serv_cover = add_preload_service(self, SERV_WINDOW_COVERING)
|
||||
self.char_current_position = setup_char(
|
||||
CHAR_CURRENT_POSITION, serv_cover, value=0)
|
||||
self.char_target_position = setup_char(
|
||||
CHAR_TARGET_POSITION, serv_cover, value=0,
|
||||
callback=self.move_cover)
|
||||
self.char_position_state = setup_char(
|
||||
CHAR_POSITION_STATE, serv_cover, value=2)
|
||||
|
||||
def move_cover(self, value):
|
||||
"""Move cover to value if call came from HomeKit."""
|
||||
_LOGGER.debug('%s: Set position to %d', self.entity_id, value)
|
||||
|
||||
if self.supports_stop:
|
||||
if value > 70:
|
||||
service, position = (SERVICE_OPEN_COVER, 100)
|
||||
elif value < 30:
|
||||
service, position = (SERVICE_CLOSE_COVER, 0)
|
||||
else:
|
||||
service, position = (SERVICE_STOP_COVER, 50)
|
||||
else:
|
||||
if value >= 50:
|
||||
service, position = (SERVICE_OPEN_COVER, 100)
|
||||
else:
|
||||
service, position = (SERVICE_CLOSE_COVER, 0)
|
||||
|
||||
self.hass.services.call(DOMAIN, service,
|
||||
{ATTR_ENTITY_ID: self.entity_id})
|
||||
|
||||
# Snap the current/target position to the expected final position.
|
||||
self.char_current_position.set_value(position)
|
||||
self.char_target_position.set_value(position)
|
||||
self.char_position_state.set_value(2)
|
||||
|
||||
def update_state(self, new_state):
|
||||
"""Update cover position after state changed."""
|
||||
position_mapping = {STATE_OPEN: 100, STATE_CLOSED: 0}
|
||||
hk_position = position_mapping.get(new_state.state)
|
||||
if hk_position is not None:
|
||||
self.char_current_position.set_value(hk_position)
|
||||
self.char_target_position.set_value(hk_position)
|
||||
self.char_position_state.set_value(2)
|
||||
|
|
|
@ -154,6 +154,13 @@ class TestGetAccessories(unittest.TestCase):
|
|||
{ATTR_SUPPORTED_FEATURES: 4})
|
||||
get_accessory(None, state, 2, {})
|
||||
|
||||
def test_cover_open_close(self):
|
||||
"""Test cover with support for open and close."""
|
||||
with patch.dict(TYPES, {'WindowCoveringBasic': self.mock_type}):
|
||||
state = State('cover.open_window', 'open',
|
||||
{ATTR_SUPPORTED_FEATURES: 3})
|
||||
get_accessory(None, state, 2, {})
|
||||
|
||||
def test_alarm_control_panel(self):
|
||||
"""Test alarm control panel."""
|
||||
config = {ATTR_CODE: '1234'}
|
||||
|
|
|
@ -3,12 +3,13 @@ import unittest
|
|||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_POSITION, ATTR_CURRENT_POSITION)
|
||||
ATTR_POSITION, ATTR_CURRENT_POSITION, SUPPORT_STOP)
|
||||
from homeassistant.components.homekit.type_covers import (
|
||||
GarageDoorOpener, WindowCovering)
|
||||
GarageDoorOpener, WindowCovering, WindowCoveringBasic)
|
||||
from homeassistant.const import (
|
||||
STATE_CLOSED, STATE_UNAVAILABLE, STATE_UNKNOWN, STATE_OPEN,
|
||||
ATTR_SERVICE, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE)
|
||||
ATTR_SERVICE, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE,
|
||||
ATTR_SUPPORTED_FEATURES)
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
@ -132,9 +133,117 @@ class TestHomekitSensors(unittest.TestCase):
|
|||
acc.char_target_position.client_update_value(75)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_SERVICE], 'set_cover_position')
|
||||
self.events[1].data[ATTR_SERVICE], 'set_cover_position')
|
||||
self.assertEqual(
|
||||
self.events[1].data[ATTR_SERVICE_DATA][ATTR_POSITION], 75)
|
||||
|
||||
self.assertEqual(acc.char_current_position.value, 50)
|
||||
self.assertEqual(acc.char_target_position.value, 75)
|
||||
|
||||
def test_window_open_close(self):
|
||||
"""Test if accessory and HA are updated accordingly."""
|
||||
window_cover = 'cover.window'
|
||||
|
||||
self.hass.states.set(window_cover, STATE_UNKNOWN,
|
||||
{ATTR_SUPPORTED_FEATURES: 0})
|
||||
acc = WindowCoveringBasic(self.hass, 'Cover', window_cover, 2,
|
||||
config=None)
|
||||
acc.run()
|
||||
|
||||
self.assertEqual(acc.aid, 2)
|
||||
self.assertEqual(acc.category, 14) # WindowCovering
|
||||
|
||||
self.assertEqual(acc.char_current_position.value, 0)
|
||||
self.assertEqual(acc.char_target_position.value, 0)
|
||||
self.assertEqual(acc.char_position_state.value, 2)
|
||||
|
||||
self.hass.states.set(window_cover, STATE_UNKNOWN)
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.assertEqual(acc.char_current_position.value, 0)
|
||||
self.assertEqual(acc.char_target_position.value, 0)
|
||||
self.assertEqual(acc.char_position_state.value, 2)
|
||||
|
||||
self.hass.states.set(window_cover, STATE_OPEN)
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.assertEqual(acc.char_current_position.value, 100)
|
||||
self.assertEqual(acc.char_target_position.value, 100)
|
||||
self.assertEqual(acc.char_position_state.value, 2)
|
||||
|
||||
self.hass.states.set(window_cover, STATE_CLOSED)
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.assertEqual(acc.char_current_position.value, 0)
|
||||
self.assertEqual(acc.char_target_position.value, 0)
|
||||
self.assertEqual(acc.char_position_state.value, 2)
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_target_position.client_update_value(25)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_SERVICE], 'close_cover')
|
||||
|
||||
self.assertEqual(acc.char_current_position.value, 0)
|
||||
self.assertEqual(acc.char_target_position.value, 0)
|
||||
self.assertEqual(acc.char_position_state.value, 2)
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_target_position.client_update_value(90)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[1].data[ATTR_SERVICE], 'open_cover')
|
||||
|
||||
self.assertEqual(acc.char_current_position.value, 100)
|
||||
self.assertEqual(acc.char_target_position.value, 100)
|
||||
self.assertEqual(acc.char_position_state.value, 2)
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_target_position.client_update_value(55)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[2].data[ATTR_SERVICE], 'open_cover')
|
||||
|
||||
self.assertEqual(acc.char_current_position.value, 100)
|
||||
self.assertEqual(acc.char_target_position.value, 100)
|
||||
self.assertEqual(acc.char_position_state.value, 2)
|
||||
|
||||
def test_window_open_close_stop(self):
|
||||
"""Test if accessory and HA are updated accordingly."""
|
||||
window_cover = 'cover.window'
|
||||
|
||||
self.hass.states.set(window_cover, STATE_UNKNOWN,
|
||||
{ATTR_SUPPORTED_FEATURES: SUPPORT_STOP})
|
||||
acc = WindowCoveringBasic(self.hass, 'Cover', window_cover, 2,
|
||||
config=None)
|
||||
acc.run()
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_target_position.client_update_value(25)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_SERVICE], 'close_cover')
|
||||
|
||||
self.assertEqual(acc.char_current_position.value, 0)
|
||||
self.assertEqual(acc.char_target_position.value, 0)
|
||||
self.assertEqual(acc.char_position_state.value, 2)
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_target_position.client_update_value(90)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[1].data[ATTR_SERVICE], 'open_cover')
|
||||
|
||||
self.assertEqual(acc.char_current_position.value, 100)
|
||||
self.assertEqual(acc.char_target_position.value, 100)
|
||||
self.assertEqual(acc.char_position_state.value, 2)
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_target_position.client_update_value(55)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[2].data[ATTR_SERVICE], 'stop_cover')
|
||||
|
||||
self.assertEqual(acc.char_current_position.value, 50)
|
||||
self.assertEqual(acc.char_target_position.value, 50)
|
||||
self.assertEqual(acc.char_position_state.value, 2)
|
||||
|
|
Loading…
Reference in New Issue