Added support for Locks, including those connected through a wink hub.
This commit is contained in:
parent
573dfb648f
commit
c78899c4f3
6 changed files with 264 additions and 1 deletions
124
homeassistant/components/lock/__init__.py
Normal file
124
homeassistant/components/lock/__init__.py
Normal 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
|
56
homeassistant/components/lock/demo.py
Normal file
56
homeassistant/components/lock/demo.py
Normal 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()
|
0
homeassistant/components/lock/services.yaml
Normal file
0
homeassistant/components/lock/services.yaml
Normal file
73
homeassistant/components/lock/wink.py
Normal file
73
homeassistant/components/lock/wink.py
Normal 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)
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue