Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions homeassistant/components/homekit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

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,
Expand Down Expand Up @@ -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':
Expand Down
4 changes: 4 additions & 0 deletions homeassistant/components/homekit/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

# #### Categories ####
CATEGORY_ALARM_SYSTEM = 'ALARM_SYSTEM'
CATEGORY_GARAGE_DOOR_OPENER = 'GARAGE_DOOR_OPENER'
CATEGORY_LIGHT = 'LIGHTBULB'
CATEGORY_LOCK = 'DOOR_LOCK'
CATEGORY_SENSOR = 'SENSOR'
Expand All @@ -38,6 +39,7 @@
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'
Expand Down Expand Up @@ -65,6 +67,7 @@
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
Expand All @@ -85,6 +88,7 @@
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'
Expand Down
50 changes: 48 additions & 2 deletions homeassistant/components/homekit/type_covers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,63 @@

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.
Expand Down
11 changes: 11 additions & 0 deletions tests/components/homekit/test_get_accessories.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
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
Expand Down Expand Up @@ -136,6 +138,15 @@ def test_device_tracker(self):
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}):
Expand Down
63 changes: 61 additions & 2 deletions tests/components/homekit/test_type_covers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
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
Expand All @@ -31,6 +32,64 @@ def tearDown(self):
"""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'
Expand Down