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
9 changes: 6 additions & 3 deletions homeassistant/components/homematic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
DISCOVER_BINARY_SENSORS = 'homematic.binary_sensor'
DISCOVER_COVER = 'homematic.cover'
DISCOVER_CLIMATE = 'homematic.climate'
DISCOVER_LOCKS = 'homematic.locks'

ATTR_DISCOVER_DEVICES = 'devices'
ATTR_PARAM = 'param'
Expand All @@ -59,7 +60,7 @@
HM_DEVICE_TYPES = {
DISCOVER_SWITCHES: [
'Switch', 'SwitchPowermeter', 'IOSwitch', 'IPSwitch', 'RFSiren',
'IPSwitchPowermeter', 'KeyMatic', 'HMWIOSwitch', 'Rain', 'EcoLogic'],
'IPSwitchPowermeter', 'HMWIOSwitch', 'Rain', 'EcoLogic'],
DISCOVER_LIGHTS: ['Dimmer', 'KeyDimmer', 'IPKeyDimmer'],
DISCOVER_SENSORS: [
'SwitchPowermeter', 'Motion', 'MotionV2', 'RemoteMotion', 'MotionIP',
Expand All @@ -78,7 +79,8 @@
'MotionIP', 'RemoteMotion', 'WeatherSensor', 'TiltSensor',
'IPShutterContact', 'HMWIOSwitch', 'MaxShutterContact', 'Rain',
'WiredSensor', 'PresenceIP'],
DISCOVER_COVER: ['Blind', 'KeyBlind', 'IPKeyBlind', 'IPKeyBlindTilt']
DISCOVER_COVER: ['Blind', 'KeyBlind', 'IPKeyBlind', 'IPKeyBlindTilt'],
DISCOVER_LOCKS: ['KeyMatic']
}

HM_IGNORE_DISCOVERY_NODE = [
Expand Down Expand Up @@ -464,7 +466,8 @@ def _system_callback_handler(hass, config, src, *args):
('cover', DISCOVER_COVER),
('binary_sensor', DISCOVER_BINARY_SENSORS),
('sensor', DISCOVER_SENSORS),
('climate', DISCOVER_CLIMATE)):
('climate', DISCOVER_CLIMATE),
('lock', DISCOVER_LOCKS)):
# Get all devices of a specific type
found_devices = _get_devices(
hass, discovery_type, addresses, interface)
Expand Down
33 changes: 32 additions & 1 deletion homeassistant/components/lock/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED,
STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK)
STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK, SERVICE_OPEN)
from homeassistant.components import group

ATTR_CHANGED_BY = 'changed_by'
Expand All @@ -39,6 +39,9 @@
vol.Optional(ATTR_CODE): cv.string,
})

# Bitfield of features supported by the lock entity
SUPPORT_OPEN = 1

_LOGGER = logging.getLogger(__name__)

PROP_TO_ATTR = {
Expand Down Expand Up @@ -78,6 +81,18 @@ def unlock(hass, entity_id=None, code=None):
hass.services.call(DOMAIN, SERVICE_UNLOCK, data)


@bind_hass
def open_lock(hass, entity_id=None, code=None):
"""Open all or specified locks."""
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id

hass.services.call(DOMAIN, SERVICE_OPEN, data)


@asyncio.coroutine
def async_setup(hass, config):
"""Track states and offer events for locks."""
Expand All @@ -97,6 +112,8 @@ def async_handle_lock_service(service):
for entity in target_locks:
if service.service == SERVICE_LOCK:
yield from entity.async_lock(code=code)
elif service.service == SERVICE_OPEN:
yield from entity.async_open(code=code)
else:
yield from entity.async_unlock(code=code)

Expand All @@ -113,6 +130,9 @@ def async_handle_lock_service(service):
hass.services.async_register(
DOMAIN, SERVICE_LOCK, async_handle_lock_service,
schema=LOCK_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_OPEN, async_handle_lock_service,
schema=LOCK_SERVICE_SCHEMA)

return True

Expand Down Expand Up @@ -158,6 +178,17 @@ def async_unlock(self, **kwargs):
"""
return self.hass.async_add_job(ft.partial(self.unlock, **kwargs))

def open(self, **kwargs):
"""Open the door latch."""
raise NotImplementedError()

def async_open(self, **kwargs):
"""Open the door latch.

This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(ft.partial(self.open, **kwargs))

@property
def state_attributes(self):
"""Return the state attributes."""
Expand Down
19 changes: 16 additions & 3 deletions homeassistant/components/lock/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
from homeassistant.components.lock import LockDevice
from homeassistant.components.lock import LockDevice, SUPPORT_OPEN
from homeassistant.const import (STATE_LOCKED, STATE_UNLOCKED)


Expand All @@ -13,17 +13,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Demo lock platform."""
add_devices([
DemoLock('Front Door', STATE_LOCKED),
DemoLock('Kitchen Door', STATE_UNLOCKED)
DemoLock('Kitchen Door', STATE_UNLOCKED),
DemoLock('Openable Lock', STATE_LOCKED, True)
])


class DemoLock(LockDevice):
"""Representation of a Demo lock."""

def __init__(self, name, state):
def __init__(self, name, state, openable=False):
"""Initialize the lock."""
self._name = name
self._state = state
self._openable = openable

@property
def should_poll(self):
Expand All @@ -49,3 +51,14 @@ def unlock(self, **kwargs):
"""Unlock the device."""
self._state = STATE_UNLOCKED
self.schedule_update_ha_state()

def open(self, **kwargs):
"""Open the door latch."""
self._state = STATE_UNLOCKED
self.schedule_update_ha_state()

@property
def supported_features(self):
"""Flag supported features."""
if self._openable:
return SUPPORT_OPEN
58 changes: 58 additions & 0 deletions homeassistant/components/lock/homematic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""
Support for Homematic lock.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/lock.homematic/
"""
import logging
from homeassistant.components.lock import LockDevice, SUPPORT_OPEN
from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
from homeassistant.const import STATE_UNKNOWN


_LOGGER = logging.getLogger(__name__)

DEPENDENCIES = ['homematic']


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Homematic lock platform."""
if discovery_info is None:
return

devices = []
for conf in discovery_info[ATTR_DISCOVER_DEVICES]:
devices.append(HMLock(conf))

add_devices(devices)


class HMLock(HMDevice, LockDevice):
"""Representation of a Homematic lock aka KeyMatic."""

@property
def is_locked(self):
"""Return true if the lock is locked."""
return not bool(self._hm_get_state())

def lock(self, **kwargs):
"""Lock the lock."""
self._hmdevice.lock()

def unlock(self, **kwargs):
"""Unlock the lock."""
self._hmdevice.unlock()

def open(self, **kwargs):
"""Open the door latch."""
self._hmdevice.open()

def _init_data_struct(self):
"""Generate the data dictionary (self._data) from metadata."""
self._state = "STATE"
self._data.update({self._state: STATE_UNKNOWN})

@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_OPEN
12 changes: 9 additions & 3 deletions tests/components/lock/test_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
from homeassistant.setup import setup_component
from homeassistant.components import lock

from tests.common import get_test_home_assistant


from tests.common import get_test_home_assistant, mock_service
FRONT = 'lock.front_door'
KITCHEN = 'lock.kitchen_door'
OPENABLE_LOCK = 'lock.openable_lock'


class TestLockDemo(unittest.TestCase):
Expand Down Expand Up @@ -48,3 +47,10 @@ def test_unlocking(self):
self.hass.block_till_done()

self.assertFalse(lock.is_locked(self.hass, FRONT))

def test_opening(self):
"""Test the opening of a lock."""
calls = mock_service(self.hass, lock.DOMAIN, lock.SERVICE_OPEN)
lock.open_lock(self.hass, OPENABLE_LOCK)
self.hass.block_till_done()
self.assertEqual(1, len(calls))