Support Garage Doors in HomeKit (#13796)
This commit is contained in:
parent
51bdd06d1f
commit
993866a314
5 changed files with 132 additions and 7 deletions
|
@ -8,7 +8,8 @@ from zlib import adler32
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.cover import SUPPORT_SET_POSITION
|
||||
from homeassistant.components.cover import (
|
||||
SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION)
|
||||
from homeassistant.const import (
|
||||
ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT,
|
||||
ATTR_DEVICE_CLASS, CONF_PORT, TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
||||
|
@ -92,9 +93,13 @@ def get_accessory(hass, state, aid, config):
|
|||
a_type = 'Thermostat'
|
||||
|
||||
elif state.domain == 'cover':
|
||||
# Only add covers that support set_cover_position
|
||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if features & SUPPORT_SET_POSITION:
|
||||
device_class = state.attributes.get(ATTR_DEVICE_CLASS)
|
||||
|
||||
if device_class == 'garage' and \
|
||||
features & (SUPPORT_OPEN | SUPPORT_CLOSE):
|
||||
a_type = 'GarageDoorOpener'
|
||||
elif features & SUPPORT_SET_POSITION:
|
||||
a_type = 'WindowCovering'
|
||||
|
||||
elif state.domain == 'light':
|
||||
|
|
|
@ -24,6 +24,7 @@ MANUFACTURER = 'HomeAssistant'
|
|||
|
||||
# #### Categories ####
|
||||
CATEGORY_ALARM_SYSTEM = 'ALARM_SYSTEM'
|
||||
CATEGORY_GARAGE_DOOR_OPENER = 'GARAGE_DOOR_OPENER'
|
||||
CATEGORY_LIGHT = 'LIGHTBULB'
|
||||
CATEGORY_LOCK = 'DOOR_LOCK'
|
||||
CATEGORY_SENSOR = 'SENSOR'
|
||||
|
@ -38,6 +39,7 @@ SERV_AIR_QUALITY_SENSOR = 'AirQualitySensor'
|
|||
SERV_CARBON_DIOXIDE_SENSOR = 'CarbonDioxideSensor'
|
||||
SERV_CARBON_MONOXIDE_SENSOR = 'CarbonMonoxideSensor'
|
||||
SERV_CONTACT_SENSOR = 'ContactSensor'
|
||||
SERV_GARAGE_DOOR_OPENER = 'GarageDoorOpener'
|
||||
SERV_HUMIDITY_SENSOR = 'HumiditySensor' # CurrentRelativeHumidity
|
||||
SERV_LEAK_SENSOR = 'LeakSensor'
|
||||
SERV_LIGHT_SENSOR = 'LightSensor'
|
||||
|
@ -65,6 +67,7 @@ CHAR_COLOR_TEMPERATURE = 'ColorTemperature'
|
|||
CHAR_CONTACT_SENSOR_STATE = 'ContactSensorState'
|
||||
CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature'
|
||||
CHAR_CURRENT_AMBIENT_LIGHT_LEVEL = 'CurrentAmbientLightLevel'
|
||||
CHAR_CURRENT_DOOR_STATE = 'CurrentDoorState'
|
||||
CHAR_CURRENT_HEATING_COOLING = 'CurrentHeatingCoolingState'
|
||||
CHAR_CURRENT_POSITION = 'CurrentPosition' # Int | [0, 100]
|
||||
CHAR_CURRENT_HUMIDITY = 'CurrentRelativeHumidity' # percent
|
||||
|
@ -85,6 +88,7 @@ CHAR_ON = 'On' # boolean
|
|||
CHAR_SATURATION = 'Saturation' # percent
|
||||
CHAR_SERIAL_NUMBER = 'SerialNumber'
|
||||
CHAR_SMOKE_DETECTED = 'SmokeDetected'
|
||||
CHAR_TARGET_DOOR_STATE = 'TargetDoorState'
|
||||
CHAR_TARGET_HEATING_COOLING = 'TargetHeatingCoolingState'
|
||||
CHAR_TARGET_POSITION = 'TargetPosition' # Int | [0, 100]
|
||||
CHAR_TARGET_SECURITY_STATE = 'SecuritySystemTargetState'
|
||||
|
|
|
@ -3,17 +3,63 @@ import logging
|
|||
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_CURRENT_POSITION, ATTR_POSITION, DOMAIN)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_SET_COVER_POSITION
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_SET_COVER_POSITION, STATE_OPEN, STATE_CLOSED)
|
||||
|
||||
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,
|
||||
CATEGORY_GARAGE_DOOR_OPENER, SERV_GARAGE_DOOR_OPENER,
|
||||
CHAR_CURRENT_DOOR_STATE, CHAR_TARGET_DOOR_STATE)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@TYPES.register('GarageDoorOpener')
|
||||
class GarageDoorOpener(HomeAccessory):
|
||||
"""Generate a Garage Door Opener accessory for a cover entity.
|
||||
|
||||
The cover entity must be in the 'garage' device class
|
||||
and support no more than open, close, and stop.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, config):
|
||||
"""Initialize a GarageDoorOpener accessory object."""
|
||||
super().__init__(*args, category=CATEGORY_GARAGE_DOOR_OPENER)
|
||||
self.flag_target_state = False
|
||||
|
||||
serv_garage_door = add_preload_service(self, SERV_GARAGE_DOOR_OPENER)
|
||||
self.char_current_state = setup_char(
|
||||
CHAR_CURRENT_DOOR_STATE, serv_garage_door, value=0)
|
||||
self.char_target_state = setup_char(
|
||||
CHAR_TARGET_DOOR_STATE, serv_garage_door, value=0,
|
||||
callback=self.set_state)
|
||||
|
||||
def set_state(self, value):
|
||||
"""Change garage state if call came from HomeKit."""
|
||||
_LOGGER.debug('%s: Set state to %d', self.entity_id, value)
|
||||
self.flag_target_state = True
|
||||
|
||||
if value == 0:
|
||||
self.char_current_state.set_value(3)
|
||||
self.hass.components.cover.open_cover(self.entity_id)
|
||||
elif value == 1:
|
||||
self.char_current_state.set_value(2)
|
||||
self.hass.components.cover.close_cover(self.entity_id)
|
||||
|
||||
def update_state(self, new_state):
|
||||
"""Update cover state after state changed."""
|
||||
hass_state = new_state.state
|
||||
if hass_state in (STATE_OPEN, STATE_CLOSED):
|
||||
current_state = 0 if hass_state == STATE_OPEN else 1
|
||||
self.char_current_state.set_value(current_state)
|
||||
if not self.flag_target_state:
|
||||
self.char_target_state.set_value(current_state)
|
||||
self.flag_target_state = False
|
||||
|
||||
|
||||
@TYPES.register('WindowCovering')
|
||||
class WindowCovering(HomeAccessory):
|
||||
"""Generate a Window accessory for a cover entity.
|
||||
|
|
|
@ -4,6 +4,8 @@ import unittest
|
|||
from unittest.mock import patch, Mock
|
||||
|
||||
from homeassistant.core import State
|
||||
from homeassistant.components.cover import (
|
||||
SUPPORT_OPEN, SUPPORT_CLOSE)
|
||||
from homeassistant.components.climate import (
|
||||
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW)
|
||||
from homeassistant.components.homekit import get_accessory, TYPES
|
||||
|
@ -136,6 +138,15 @@ class TestGetAccessories(unittest.TestCase):
|
|||
state = State('device_tracker.someone', 'not_home', {})
|
||||
get_accessory(None, state, 2, {})
|
||||
|
||||
def test_garage_door(self):
|
||||
"""Test cover with device_class: 'garage' and required features."""
|
||||
with patch.dict(TYPES, {'GarageDoorOpener': self.mock_type}):
|
||||
state = State('cover.garage_door', 'open', {
|
||||
ATTR_DEVICE_CLASS: 'garage',
|
||||
ATTR_SUPPORTED_FEATURES:
|
||||
SUPPORT_OPEN | SUPPORT_CLOSE})
|
||||
get_accessory(None, state, 2, {})
|
||||
|
||||
def test_cover_set_position(self):
|
||||
"""Test cover with support for set_cover_position."""
|
||||
with patch.dict(TYPES, {'WindowCovering': self.mock_type}):
|
||||
|
|
|
@ -4,9 +4,10 @@ import unittest
|
|||
from homeassistant.core import callback
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_POSITION, ATTR_CURRENT_POSITION)
|
||||
from homeassistant.components.homekit.type_covers import WindowCovering
|
||||
from homeassistant.components.homekit.type_covers import (
|
||||
GarageDoorOpener, WindowCovering)
|
||||
from homeassistant.const import (
|
||||
STATE_UNKNOWN, STATE_OPEN,
|
||||
STATE_CLOSED, STATE_UNAVAILABLE, STATE_UNKNOWN, STATE_OPEN,
|
||||
ATTR_SERVICE, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE)
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
@ -31,6 +32,64 @@ class TestHomekitSensors(unittest.TestCase):
|
|||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_garage_door_open_close(self):
|
||||
"""Test if accessory and HA are updated accordingly."""
|
||||
garage_door = 'cover.garage_door'
|
||||
|
||||
acc = GarageDoorOpener(self.hass, 'Cover', garage_door, 2, config=None)
|
||||
acc.run()
|
||||
|
||||
self.assertEqual(acc.aid, 2)
|
||||
self.assertEqual(acc.category, 4) # GarageDoorOpener
|
||||
|
||||
self.assertEqual(acc.char_current_state.value, 0)
|
||||
self.assertEqual(acc.char_target_state.value, 0)
|
||||
|
||||
self.hass.states.set(garage_door, STATE_CLOSED)
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.assertEqual(acc.char_current_state.value, 1)
|
||||
self.assertEqual(acc.char_target_state.value, 1)
|
||||
|
||||
self.hass.states.set(garage_door, STATE_OPEN)
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.assertEqual(acc.char_current_state.value, 0)
|
||||
self.assertEqual(acc.char_target_state.value, 0)
|
||||
|
||||
self.hass.states.set(garage_door, STATE_UNAVAILABLE)
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.assertEqual(acc.char_current_state.value, 0)
|
||||
self.assertEqual(acc.char_target_state.value, 0)
|
||||
|
||||
self.hass.states.set(garage_door, STATE_UNKNOWN)
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.assertEqual(acc.char_current_state.value, 0)
|
||||
self.assertEqual(acc.char_target_state.value, 0)
|
||||
|
||||
# Set closed from HomeKit
|
||||
acc.char_target_state.client_update_value(1)
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.assertEqual(acc.char_current_state.value, 2)
|
||||
self.assertEqual(acc.char_target_state.value, 1)
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_SERVICE], 'close_cover')
|
||||
|
||||
self.hass.states.set(garage_door, STATE_CLOSED)
|
||||
self.hass.block_till_done()
|
||||
|
||||
# Set open from HomeKit
|
||||
acc.char_target_state.client_update_value(0)
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.assertEqual(acc.char_current_state.value, 3)
|
||||
self.assertEqual(acc.char_target_state.value, 0)
|
||||
self.assertEqual(
|
||||
self.events[1].data[ATTR_SERVICE], 'open_cover')
|
||||
|
||||
def test_window_set_cover_position(self):
|
||||
"""Test if accessory and HA are updated accordingly."""
|
||||
window_cover = 'cover.window'
|
||||
|
|
Loading…
Add table
Reference in a new issue