Add Homekit locks support (#13625)
* homekit: Add locks support * Improved upgradeability
This commit is contained in:
parent
73de749411
commit
c61611d2b4
4 changed files with 164 additions and 2 deletions
|
@ -127,6 +127,9 @@ def get_accessory(hass, state, aid, config):
|
|||
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, 'Light')
|
||||
return TYPES['Light'](hass, state.entity_id, state.name, aid=aid)
|
||||
|
||||
elif state.domain == 'lock':
|
||||
return TYPES['Lock'](hass, state.entity_id, state.name, aid=aid)
|
||||
|
||||
elif state.domain == 'switch' or state.domain == 'remote' \
|
||||
or state.domain == 'input_boolean' or state.domain == 'script':
|
||||
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, 'Switch')
|
||||
|
@ -186,8 +189,8 @@ class HomeKit():
|
|||
|
||||
# pylint: disable=unused-variable
|
||||
from . import ( # noqa F401
|
||||
type_covers, type_lights, type_security_systems, type_sensors,
|
||||
type_switches, type_thermostats)
|
||||
type_covers, type_lights, type_locks, type_security_systems,
|
||||
type_sensors, type_switches, type_thermostats)
|
||||
|
||||
for state in self._hass.states.all():
|
||||
self.add_bridge_accessory(state)
|
||||
|
|
|
@ -27,6 +27,7 @@ MANUFACTURER = 'HomeAssistant'
|
|||
# #### Categories ####
|
||||
CATEGORY_ALARM_SYSTEM = 'ALARM_SYSTEM'
|
||||
CATEGORY_LIGHT = 'LIGHTBULB'
|
||||
CATEGORY_LOCK = 'DOOR_LOCK'
|
||||
CATEGORY_SENSOR = 'SENSOR'
|
||||
CATEGORY_SWITCH = 'SWITCH'
|
||||
CATEGORY_THERMOSTAT = 'THERMOSTAT'
|
||||
|
@ -43,6 +44,7 @@ SERV_HUMIDITY_SENSOR = 'HumiditySensor'
|
|||
# StatusLowBattery, Name
|
||||
SERV_LEAK_SENSOR = 'LeakSensor'
|
||||
SERV_LIGHTBULB = 'Lightbulb' # On | Brightness, Hue, Saturation, Name
|
||||
SERV_LOCK = 'LockMechanism'
|
||||
SERV_MOTION_SENSOR = 'MotionSensor'
|
||||
SERV_OCCUPANCY_SENSOR = 'OccupancySensor'
|
||||
SERV_SECURITY_SYSTEM = 'SecuritySystem'
|
||||
|
@ -68,6 +70,9 @@ CHAR_CURRENT_TEMPERATURE = 'CurrentTemperature'
|
|||
CHAR_HEATING_THRESHOLD_TEMPERATURE = 'HeatingThresholdTemperature'
|
||||
CHAR_HUE = 'Hue' # arcdegress | [0, 360]
|
||||
CHAR_LEAK_DETECTED = 'LeakDetected'
|
||||
CHAR_LOCK_CURRENT_STATE = 'LockCurrentState'
|
||||
CHAR_LOCK_TARGET_STATE = 'LockTargetState'
|
||||
CHAR_LINK_QUALITY = 'LinkQuality'
|
||||
CHAR_MANUFACTURER = 'Manufacturer'
|
||||
CHAR_MODEL = 'Model'
|
||||
CHAR_MOTION_DETECTED = 'MotionDetected'
|
||||
|
|
77
homeassistant/components/homekit/type_locks.py
Normal file
77
homeassistant/components/homekit/type_locks.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
"""Class to hold all lock accessories."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.lock import (
|
||||
ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN)
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory, add_preload_service
|
||||
from .const import (
|
||||
CATEGORY_LOCK, SERV_LOCK, CHAR_LOCK_CURRENT_STATE, CHAR_LOCK_TARGET_STATE)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
HASS_TO_HOMEKIT = {STATE_UNLOCKED: 0,
|
||||
STATE_LOCKED: 1,
|
||||
# value 2 is Jammed which hass doesn't have a state for
|
||||
STATE_UNKNOWN: 3}
|
||||
HOMEKIT_TO_HASS = {c: s for s, c in HASS_TO_HOMEKIT.items()}
|
||||
STATE_TO_SERVICE = {STATE_LOCKED: 'lock',
|
||||
STATE_UNLOCKED: 'unlock'}
|
||||
|
||||
|
||||
@TYPES.register('Lock')
|
||||
class Lock(HomeAccessory):
|
||||
"""Generate a Lock accessory for a lock entity.
|
||||
|
||||
The lock entity must support: unlock and lock.
|
||||
"""
|
||||
|
||||
def __init__(self, hass, entity_id, name, **kwargs):
|
||||
"""Initialize a Lock accessory object."""
|
||||
super().__init__(name, entity_id, CATEGORY_LOCK, **kwargs)
|
||||
|
||||
self.hass = hass
|
||||
self.entity_id = entity_id
|
||||
|
||||
self.flag_target_state = False
|
||||
|
||||
serv_lock_mechanism = add_preload_service(self, SERV_LOCK)
|
||||
self.char_current_state = serv_lock_mechanism. \
|
||||
get_characteristic(CHAR_LOCK_CURRENT_STATE)
|
||||
self.char_target_state = serv_lock_mechanism. \
|
||||
get_characteristic(CHAR_LOCK_TARGET_STATE)
|
||||
|
||||
self.char_current_state.value = HASS_TO_HOMEKIT[STATE_UNKNOWN]
|
||||
self.char_target_state.value = HASS_TO_HOMEKIT[STATE_LOCKED]
|
||||
|
||||
self.char_target_state.setter_callback = self.set_state
|
||||
|
||||
def set_state(self, value):
|
||||
"""Set lock state to value if call came from HomeKit."""
|
||||
_LOGGER.debug("%s: Set state to %d", self.entity_id, value)
|
||||
self.flag_target_state = True
|
||||
|
||||
hass_value = HOMEKIT_TO_HASS.get(value)
|
||||
service = STATE_TO_SERVICE[hass_value]
|
||||
|
||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||
self.hass.services.call('lock', service, params)
|
||||
|
||||
def update_state(self, entity_id=None, old_state=None, new_state=None):
|
||||
"""Update lock after state changed."""
|
||||
if new_state is None:
|
||||
return
|
||||
|
||||
hass_state = new_state.state
|
||||
if hass_state in HASS_TO_HOMEKIT:
|
||||
current_lock_state = HASS_TO_HOMEKIT[hass_state]
|
||||
self.char_current_state.set_value(current_lock_state)
|
||||
_LOGGER.debug('%s: Updated current state to %s (%d)',
|
||||
self.entity_id, hass_state, current_lock_state)
|
||||
|
||||
# LockTargetState only supports locked and unlocked
|
||||
if hass_state in (STATE_LOCKED, STATE_UNLOCKED):
|
||||
if not self.flag_target_state:
|
||||
self.char_target_state.set_value(current_lock_state)
|
||||
self.flag_target_state = False
|
77
tests/components/homekit/test_type_locks.py
Normal file
77
tests/components/homekit/test_type_locks.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
"""Test different accessory types: Locks."""
|
||||
import unittest
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.homekit.type_locks import Lock
|
||||
from homeassistant.const import (
|
||||
STATE_UNKNOWN, STATE_UNLOCKED, STATE_LOCKED,
|
||||
ATTR_SERVICE, EVENT_CALL_SERVICE)
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
class TestHomekitSensors(unittest.TestCase):
|
||||
"""Test class for all accessory types regarding covers."""
|
||||
|
||||
def setUp(self):
|
||||
"""Setup things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.events = []
|
||||
|
||||
@callback
|
||||
def record_event(event):
|
||||
"""Track called event."""
|
||||
self.events.append(event)
|
||||
|
||||
self.hass.bus.listen(EVENT_CALL_SERVICE, record_event)
|
||||
|
||||
def tearDown(self):
|
||||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_lock_unlock(self):
|
||||
"""Test if accessory and HA are updated accordingly."""
|
||||
kitchen_lock = 'lock.kitchen_door'
|
||||
|
||||
acc = Lock(self.hass, kitchen_lock, 'Lock', aid=2)
|
||||
acc.run()
|
||||
|
||||
self.assertEqual(acc.aid, 2)
|
||||
self.assertEqual(acc.category, 6) # DoorLock
|
||||
|
||||
self.assertEqual(acc.char_current_state.value, 3)
|
||||
self.assertEqual(acc.char_target_state.value, 1)
|
||||
|
||||
self.hass.states.set(kitchen_lock, STATE_LOCKED)
|
||||
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(kitchen_lock, STATE_UNLOCKED)
|
||||
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(kitchen_lock, STATE_UNKNOWN)
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.assertEqual(acc.char_current_state.value, 3)
|
||||
self.assertEqual(acc.char_target_state.value, 0)
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_target_state.client_update_value(1)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_SERVICE], 'lock')
|
||||
self.assertEqual(acc.char_target_state.value, 1)
|
||||
|
||||
acc.char_target_state.client_update_value(0)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[1].data[ATTR_SERVICE], 'unlock')
|
||||
self.assertEqual(acc.char_target_state.value, 0)
|
||||
|
||||
self.hass.states.remove(kitchen_lock)
|
||||
self.hass.block_till_done()
|
Loading…
Add table
Add a link
Reference in a new issue