Added support for Locks, including those connected through a wink hub.

This commit is contained in:
miniconfig 2015-11-17 10:17:57 -05:00
parent 573dfb648f
commit c78899c4f3
6 changed files with 264 additions and 1 deletions

View file

@ -0,0 +1,124 @@
"""
homeassistant.components.lock
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component to interface with various locks that can be controlled remotely.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/lock/
"""
from datetime import timedelta
import logging
import os
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.const import (
STATE_LOCKED, SERVICE_LOCK, SERVICE_UNLOCK, ATTR_ENTITY_ID)
from homeassistant.components import (
group, wink)
DOMAIN = 'lock'
DEPENDENCIES = []
SCAN_INTERVAL = 30
GROUP_NAME_ALL_LOCKS = 'all locks'
ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format('all_locks')
ENTITY_ID_FORMAT = DOMAIN + '.{}'
ATTR_LOCKED = "locked"
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
# Maps discovered services to their platforms
DISCOVERY_PLATFORMS = {
wink.DISCOVER_LOCKS: 'wink'
}
PROP_TO_ATTR = {
'locked': ATTR_LOCKED
}
_LOGGER = logging.getLogger(__name__)
def is_locked(hass, entity_id=None):
""" Returns if the lock is locked based on the statemachine. """
entity_id = entity_id or ENTITY_ID_ALL_LOCKS
return hass.states.is_state(entity_id, STATE_LOCKED)
def do_lock(hass, entity_id=None):
""" Locks all or specified locks. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_LOCK, data)
def do_unlock(hass, entity_id=None):
""" Unlocks all or specified locks. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.services.call(DOMAIN, SERVICE_UNLOCK, data)
def setup(hass, config):
""" Track states and offer events for locks. """
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS,
GROUP_NAME_ALL_LOCKS)
component.setup(config)
def handle_lock_service(service):
""" Handles calls to the lock services. """
target_locks = component.extract_from_service(service)
for lock in target_locks:
if service.service == SERVICE_LOCK:
lock.do_lock()
else:
lock.do_unlock()
if lock.should_poll:
lock.update_ha_state(True)
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
hass.services.register(DOMAIN, SERVICE_UNLOCK, handle_lock_service,
descriptions.get(SERVICE_UNLOCK))
hass.services.register(DOMAIN, SERVICE_LOCK, handle_lock_service,
descriptions.get(SERVICE_LOCK))
return True
class LockDevice(ToggleEntity):
""" Represents a lock within Home Assistant. """
# pylint: disable=no-self-use
@property
def locked(self):
""" Is the lock locked or unlocked. """
return None
@property
def device_state_attributes(self):
""" Returns device specific state attributes. """
return None
@property
def state_attributes(self):
""" Returns optional state attributes. """
data = {}
for prop, attr in PROP_TO_ATTR.items():
value = getattr(self, prop)
if value:
data[attr] = value
device_attr = self.device_state_attributes
if device_attr is not None:
data.update(device_attr)
return data

View file

@ -0,0 +1,56 @@
"""
homeassistant.components.lock.demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demo platform that has two fake locks.
"""
from homeassistant.components.lock import LockDevice
from homeassistant.const import (
DEVICE_DEFAULT_NAME, STATE_LOCKED, STATE_UNLOCKED)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return demo locks. """
add_devices_callback([
DemoLock('Left Door', STATE_LOCKED, None),
DemoLock('Right Door', STATE_UNLOCKED, None)
])
class DemoLock(LockDevice):
""" Provides a demo lock. """
def __init__(self, name, state, icon):
self._name = name or DEVICE_DEFAULT_NAME
self._state = state
self._icon = icon
@property
def should_poll(self):
""" No polling needed for a demo lock. """
return False
@property
def name(self):
""" Returns the name of the device if any. """
return self._name
@property
def icon(self):
""" Returns the icon to use for device if any. """
return self._icon
@property
def is_locked(self):
""" True if device is locked. """
return self._state
def do_lock(self, **kwargs):
""" Lock the device. """
self._state = STATE_LOCKED
self.update_ha_state()
def do_unlock(self, **kwargs):
""" Unlock the device. """
self._state = STATE_UNLOCKED
self.update_ha_state()

View file

@ -0,0 +1,73 @@
"""
homeassistant.components.lock.wink
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Wink locks.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/lock.wink/
"""
import logging
from homeassistant.helpers.entity import Entity
from homeassistant.const import CONF_ACCESS_TOKEN, STATE_LOCKED, STATE_UNLOCKED
REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/'
'9eb39eaba0717922815e673ad1114c685839d890.zip'
'#python-wink==0.1.1']
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Wink platform. """
import pywink
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token. "
"Get one at https://winkbearertoken.appspot.com/")
return
pywink.set_bearer_token(token)
add_devices(WinkLockDevice(lock) for lock in pywink.get_locks())
class WinkLockDevice(Entity):
""" Represents a Wink lock. """
def __init__(self, wink):
self.wink = wink
@property
def state(self):
""" Returns the state. """
return STATE_LOCKED if self.is_locked else STATE_UNLOCKED
@property
def unique_id(self):
""" Returns the id of this wink lock """
return "{}.{}".format(self.__class__, self.wink.deviceId())
@property
def name(self):
""" Returns the name of the lock if any. """
return self.wink.name()
def update(self):
""" Update the state of the lock. """
self.wink.updateState()
@property
def is_locked(self):
""" True if device is locked. """
return self.wink.state()
def do_lock(self):
""" Lock the device. """
self.wink.setState(True)
def do_unlock(self):
""" Unlock the device. """
self.wink.setState(False)

View file

@ -25,6 +25,7 @@ REQUIREMENTS = ['https://github.com/balloob/python-wink/archive/'
DISCOVER_LIGHTS = "wink.lights"
DISCOVER_SWITCHES = "wink.switches"
DISCOVER_SENSORS = "wink.sensors"
DISCOVER_LOCKS = "wink.locks"
def setup(hass, config):
@ -41,7 +42,8 @@ def setup(hass, config):
for component_name, func_exists, discovery_type in (
('light', pywink.get_bulbs, DISCOVER_LIGHTS),
('switch', pywink.get_switches, DISCOVER_SWITCHES),
('sensor', pywink.get_sensors, DISCOVER_SENSORS)):
('sensor', pywink.get_sensors, DISCOVER_SENSORS),
('lock', pywink.get_locks, DISCOVER_LOCKS)):
if func_exists():
component = get_component(component_name)

View file

@ -53,6 +53,8 @@ STATE_ALARM_ARMED_HOME = 'armed_home'
STATE_ALARM_ARMED_AWAY = 'armed_away'
STATE_ALARM_PENDING = 'pending'
STATE_ALARM_TRIGGERED = 'triggered'
STATE_LOCKED = 'locked'
STATE_UNLOCKED = 'unlocked'
# #### STATE AND EVENT ATTRIBUTES ####
# Contains current time for a TIME_CHANGED event
@ -96,6 +98,9 @@ ATTR_BATTERY_LEVEL = "battery_level"
# For devices which support an armed state
ATTR_ARMED = "device_armed"
# For devices which support a locked state
ATTR_LOCKED = "locked"
# For sensors that support 'tripping', eg. motion and door sensors
ATTR_TRIPPED = "device_tripped"
@ -135,6 +140,9 @@ SERVICE_ALARM_ARM_HOME = "alarm_arm_home"
SERVICE_ALARM_ARM_AWAY = "alarm_arm_away"
SERVICE_ALARM_TRIGGER = "alarm_trigger"
SERVICE_LOCK = "lock"
SERVICE_UNLOCK = "unlock"
# #### API / REMOTE ####
SERVER_PORT = 8123