From 2c9c79ea61cb16c5dbbef7482b57667dc2281734 Mon Sep 17 00:00:00 2001 From: Chris Mulder Date: Sun, 9 Aug 2015 18:10:34 +0200 Subject: [PATCH 01/33] Add support for TP-Link ArcherC9 to to device tracker component --- .../components/device_tracker/tplink2.py | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100755 homeassistant/components/device_tracker/tplink2.py diff --git a/homeassistant/components/device_tracker/tplink2.py b/homeassistant/components/device_tracker/tplink2.py new file mode 100755 index 00000000000..834b1f210ae --- /dev/null +++ b/homeassistant/components/device_tracker/tplink2.py @@ -0,0 +1,128 @@ +""" +homeassistant.components.device_tracker.tplink2 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Device tracker platform that supports scanning a newer model TP-Link router for device +presence. + +Configuration: + +To use the TP-Link tracker you will need to add something like the following +to your config/configuration.yaml + +device_tracker: + platform: tplink2 + host: YOUR_ROUTER_IP + username: YOUR_ADMIN_USERNAME + password: YOUR_ADMIN_PASSWORD + +Variables: + +host +*Required +The IP address of your router, e.g. 192.168.1.1. + +username +*Required +The username of an user with administrative privileges, usually 'admin'. + +password +*Required +The password for your given admin account. + +""" +import logging +import base64 +from datetime import timedelta +import threading +import requests + +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.helpers import validate_config +from homeassistant.util import Throttle +from homeassistant.components.device_tracker import DOMAIN + +# Return cached results if last scan was less then this time ago +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) + +_LOGGER = logging.getLogger(__name__) + + +def get_scanner(hass, config): + """ Validates config and returns a TP-Link scanner. """ + if not validate_config(config, + {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, + _LOGGER): + return None + + scanner = Tplink2DeviceScanner(config[DOMAIN]) + + return scanner if scanner.success_init else None + + +class Tplink2DeviceScanner(object): + """ This class queries a wireless router running TP-Link firmware + for connected devices. + """ + + def __init__(self, config): + host = config[CONF_HOST] + username, password = config[CONF_USERNAME], config[CONF_PASSWORD] + + self.host = host + self.username = username + self.password = password + + self.last_results = {} + self.lock = threading.Lock() + self.success_init = self._update_info() + + def scan_devices(self): + """ Scans for new devices and return a + list containing found device ids. """ + + self._update_info() + return self.last_results.keys() + + # pylint: disable=no-self-use + def get_device_name(self, device): + """ The TP-Link firmware doesn't save the name of the wireless + device. """ + + return self.last_results.get(device) + + @Throttle(MIN_TIME_BETWEEN_SCANS) + def _update_info(self): + """ Ensures the information from the TP-Link router is up to date. + Returns boolean if scanning successful. """ + + with self.lock: + _LOGGER.info("Loading wireless clients...") + + url = 'http://{}/data/map_access_wireless_client_grid.json'.format(self.host) + referer = 'http://{}'.format(self.host) + + # Router uses Authorization cookie instead of header + # Let's create the cookie + username_password = '{}:{}'.format(self.username, self.password) + b64_encoded_username_password = base64.b64encode( + username_password.encode('ascii') + ).decode('ascii') + authorization = 'Basic {}'.format(b64_encoded_username_password) + cookie = 'Authorization={}'.format(authorization) + + response = requests.post(url, headers={'referer': referer, + 'cookie': cookie}) + + result = None + try: + result = response.json().get('data') + except ValueError: + _LOGGER.error("Router didn't respond with JSON. Check if credentials are correct.") + + if result: + self.last_results = {device['mac_addr'].replace('-', ':'): device['name'] + for device in result} + return True + + return False From 58fcf7934025ad9c2420840c75613ad070d935e3 Mon Sep 17 00:00:00 2001 From: Chris Mulder Date: Mon, 10 Aug 2015 20:03:43 +0200 Subject: [PATCH 02/33] Put new TP-Link device tracker class in same file as original and use the new one and have tailback to original one. --- .../components/device_tracker/tplink.py | 66 ++++++++- .../components/device_tracker/tplink2.py | 128 ------------------ 2 files changed, 65 insertions(+), 129 deletions(-) delete mode 100755 homeassistant/components/device_tracker/tplink2.py diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index 24d170a5de7..8e556e47e8a 100755 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -31,6 +31,7 @@ password The password for your given admin account. """ +import base64 import logging from datetime import timedelta import re @@ -55,7 +56,10 @@ def get_scanner(hass, config): _LOGGER): return None - scanner = TplinkDeviceScanner(config[DOMAIN]) + scanner = Tplink2DeviceScanner(config[DOMAIN]) + + if not scanner.success_init: + scanner = TplinkDeviceScanner(config[DOMAIN]) return scanner if scanner.success_init else None @@ -115,3 +119,63 @@ class TplinkDeviceScanner(object): return True return False + + +class Tplink2DeviceScanner(TplinkDeviceScanner): + """ This class queries a wireless router running newer version of TP-Link + firmware for connected devices. + """ + + def scan_devices(self): + """ Scans for new devices and return a + list containing found device ids. """ + + self._update_info() + return self.last_results.keys() + + # pylint: disable=no-self-use + def get_device_name(self, device): + """ The TP-Link firmware doesn't save the name of the wireless + device. """ + + return self.last_results.get(device) + + @Throttle(MIN_TIME_BETWEEN_SCANS) + def _update_info(self): + """ Ensures the information from the TP-Link router is up to date. + Returns boolean if scanning successful. """ + + with self.lock: + _LOGGER.info("Loading wireless clients...") + + url = 'http://{}/data/map_access_wireless_client_grid.json'\ + .format(self.host) + referer = 'http://{}'.format(self.host) + + # Router uses Authorization cookie instead of header + # Let's create the cookie + username_password = '{}:{}'.format(self.username, self.password) + b64_encoded_username_password = base64.b64encode( + username_password.encode('ascii') + ).decode('ascii') + cookie = 'Authorization=Basic {}'\ + .format(b64_encoded_username_password) + + response = requests.post(url, headers={'referer': referer, + 'cookie': cookie}) + + try: + result = response.json().get('data') + except ValueError: + _LOGGER.error("Router didn't respond with JSON. " + "Check if credentials are correct.") + return False + + if result: + self.last_results = { + device['mac_addr'].replace('-', ':'): device['name'] + for device in result + } + return True + + return False diff --git a/homeassistant/components/device_tracker/tplink2.py b/homeassistant/components/device_tracker/tplink2.py deleted file mode 100755 index 834b1f210ae..00000000000 --- a/homeassistant/components/device_tracker/tplink2.py +++ /dev/null @@ -1,128 +0,0 @@ -""" -homeassistant.components.device_tracker.tplink2 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Device tracker platform that supports scanning a newer model TP-Link router for device -presence. - -Configuration: - -To use the TP-Link tracker you will need to add something like the following -to your config/configuration.yaml - -device_tracker: - platform: tplink2 - host: YOUR_ROUTER_IP - username: YOUR_ADMIN_USERNAME - password: YOUR_ADMIN_PASSWORD - -Variables: - -host -*Required -The IP address of your router, e.g. 192.168.1.1. - -username -*Required -The username of an user with administrative privileges, usually 'admin'. - -password -*Required -The password for your given admin account. - -""" -import logging -import base64 -from datetime import timedelta -import threading -import requests - -from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD -from homeassistant.helpers import validate_config -from homeassistant.util import Throttle -from homeassistant.components.device_tracker import DOMAIN - -# Return cached results if last scan was less then this time ago -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) - -_LOGGER = logging.getLogger(__name__) - - -def get_scanner(hass, config): - """ Validates config and returns a TP-Link scanner. """ - if not validate_config(config, - {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, - _LOGGER): - return None - - scanner = Tplink2DeviceScanner(config[DOMAIN]) - - return scanner if scanner.success_init else None - - -class Tplink2DeviceScanner(object): - """ This class queries a wireless router running TP-Link firmware - for connected devices. - """ - - def __init__(self, config): - host = config[CONF_HOST] - username, password = config[CONF_USERNAME], config[CONF_PASSWORD] - - self.host = host - self.username = username - self.password = password - - self.last_results = {} - self.lock = threading.Lock() - self.success_init = self._update_info() - - def scan_devices(self): - """ Scans for new devices and return a - list containing found device ids. """ - - self._update_info() - return self.last_results.keys() - - # pylint: disable=no-self-use - def get_device_name(self, device): - """ The TP-Link firmware doesn't save the name of the wireless - device. """ - - return self.last_results.get(device) - - @Throttle(MIN_TIME_BETWEEN_SCANS) - def _update_info(self): - """ Ensures the information from the TP-Link router is up to date. - Returns boolean if scanning successful. """ - - with self.lock: - _LOGGER.info("Loading wireless clients...") - - url = 'http://{}/data/map_access_wireless_client_grid.json'.format(self.host) - referer = 'http://{}'.format(self.host) - - # Router uses Authorization cookie instead of header - # Let's create the cookie - username_password = '{}:{}'.format(self.username, self.password) - b64_encoded_username_password = base64.b64encode( - username_password.encode('ascii') - ).decode('ascii') - authorization = 'Basic {}'.format(b64_encoded_username_password) - cookie = 'Authorization={}'.format(authorization) - - response = requests.post(url, headers={'referer': referer, - 'cookie': cookie}) - - result = None - try: - result = response.json().get('data') - except ValueError: - _LOGGER.error("Router didn't respond with JSON. Check if credentials are correct.") - - if result: - self.last_results = {device['mac_addr'].replace('-', ':'): device['name'] - for device in result} - return True - - return False From 3bbeeda3acea72477d869c4b26d33add87b12ee9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 10 Aug 2015 18:12:22 -0700 Subject: [PATCH 03/33] Fix MQTT wildcard topic subscriptions --- homeassistant/components/mqtt.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt.py b/homeassistant/components/mqtt.py index 7f6b0c11b5f..be50cc31175 100644 --- a/homeassistant/components/mqtt.py +++ b/homeassistant/components/mqtt.py @@ -92,8 +92,8 @@ def publish(hass, topic, payload): def subscribe(hass, topic, callback, qos=0): """ Subscribe to a topic. """ def mqtt_topic_subscriber(event): - """ Subscribes to a specific MQTT topic. """ - if event.data[ATTR_TOPIC] == topic: + """ Match subscribed MQTT topic. """ + if _match_topic(topic, event.data[ATTR_TOPIC]): callback(topic, event.data[ATTR_PAYLOAD], event.data[ATTR_QOS]) hass.bus.listen(EVENT_MQTT_MESSAGE_RECEIVED, mqtt_topic_subscriber) @@ -240,3 +240,11 @@ def _raise_on_error(result): """ Raise error if error result. """ if result != 0: raise HomeAssistantError('Error talking to MQTT: {}'.format(result)) + + +def _match_topic(subscription, topic): + """ Returns if topic matches subscription. """ + if not subscription.endswith('#'): + return subscription == topic + + return subscription[:-2] == topic or topic.startswith(subscription[:-1]) From 6e98e55f6a88457a1a30ac8171895dc78048b59f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 10 Aug 2015 21:49:19 -0700 Subject: [PATCH 04/33] MQTT: add support for + wildcard in subscribe --- homeassistant/components/mqtt.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mqtt.py b/homeassistant/components/mqtt.py index be50cc31175..98612e2bd13 100644 --- a/homeassistant/components/mqtt.py +++ b/homeassistant/components/mqtt.py @@ -94,7 +94,8 @@ def subscribe(hass, topic, callback, qos=0): def mqtt_topic_subscriber(event): """ Match subscribed MQTT topic. """ if _match_topic(topic, event.data[ATTR_TOPIC]): - callback(topic, event.data[ATTR_PAYLOAD], event.data[ATTR_QOS]) + callback(event.data[ATTR_TOPIC], event.data[ATTR_PAYLOAD], + event.data[ATTR_QOS]) hass.bus.listen(EVENT_MQTT_MESSAGE_RECEIVED, mqtt_topic_subscriber) @@ -244,7 +245,12 @@ def _raise_on_error(result): def _match_topic(subscription, topic): """ Returns if topic matches subscription. """ - if not subscription.endswith('#'): - return subscription == topic + if subscription.endswith('#'): + return (subscription[:-2] == topic or + topic.startswith(subscription[:-1])) - return subscription[:-2] == topic or topic.startswith(subscription[:-1]) + sub_parts = subscription.split('/') + topic_parts = topic.split('/') + + return (len(sub_parts) == len(topic_parts) and + all(a == b for a, b in zip(sub_parts, topic_parts) if a != '+')) From eecc51c92d480ff105b027c79818a99487aa4d1a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 10 Aug 2015 22:21:34 -0700 Subject: [PATCH 05/33] Add tests for automation component --- .../components/automation/__init__.py | 3 +- tests/components/automation/__init__.py | 0 tests/components/automation/test_event.py | 72 ++++++++++ tests/components/automation/test_state.py | 133 ++++++++++++++++++ tests/components/automation/test_time.py | 97 +++++++++++++ 5 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 tests/components/automation/__init__.py create mode 100644 tests/components/automation/test_event.py create mode 100644 tests/components/automation/test_state.py create mode 100644 tests/components/automation/test_time.py diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index c7fa1c12d4b..23d1c6f02d3 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -56,8 +56,7 @@ def _get_action(hass, config): service_data = config.get(CONF_SERVICE_DATA, {}) if not isinstance(service_data, dict): - _LOGGER.error( - "%s should be a serialized JSON object", CONF_SERVICE_DATA) + _LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA) service_data = {} if CONF_SERVICE_ENTITY_ID in config: diff --git a/tests/components/automation/__init__.py b/tests/components/automation/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py new file mode 100644 index 00000000000..9c8acda86fd --- /dev/null +++ b/tests/components/automation/test_event.py @@ -0,0 +1,72 @@ +""" +tests.test_component_demo +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests demo component. +""" +import unittest + +import homeassistant as ha +import homeassistant.loader as loader +import homeassistant.components.automation as automation +import homeassistant.components.automation.event as event +from homeassistant.const import CONF_PLATFORM + + +class TestAutomationEvent(unittest.TestCase): + """ Test the event automation. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = ha.HomeAssistant() + loader.prepare(self.hass) + self.calls = [] + + def record_call(service): + self.calls.append(service) + + self.hass.services.register('test', 'automation', record_call) + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_if_fires_on_event(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'event', + event.CONF_EVENT_TYPE: 'test_event', + automation.CONF_SERVICE: 'test.automation' + } + }) + + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_fires_on_event_with_data(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'event', + event.CONF_EVENT_TYPE: 'test_event', + event.CONF_EVENT_DATA: {'some_attr': 'some_value'}, + automation.CONF_SERVICE: 'test.automation' + } + }) + + self.hass.bus.fire('test_event', {'some_attr': 'some_value'}) + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_not_fires_if_event_data_not_matches(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'event', + event.CONF_EVENT_TYPE: 'test_event', + event.CONF_EVENT_DATA: {'some_attr': 'some_value'}, + automation.CONF_SERVICE: 'test.automation' + } + }) + + self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'}) + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py new file mode 100644 index 00000000000..a9c74555b79 --- /dev/null +++ b/tests/components/automation/test_state.py @@ -0,0 +1,133 @@ +""" +tests.test_component_demo +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests demo component. +""" +import unittest + +import homeassistant as ha +import homeassistant.loader as loader +import homeassistant.components.automation as automation +import homeassistant.components.automation.state as state +from homeassistant.const import CONF_PLATFORM + + +class TestAutomationState(unittest.TestCase): + """ Test the event automation. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = ha.HomeAssistant() + loader.prepare(self.hass) + self.hass.states.set('test.entity', 'hello') + self.calls = [] + + def record_call(service): + self.calls.append(service) + + self.hass.services.register('test', 'automation', record_call) + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_if_fires_on_entity_change(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'state', + state.CONF_ENTITY_ID: 'test.entity', + automation.CONF_SERVICE: 'test.automation' + } + }) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_fires_on_entity_change_with_from_filter(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'state', + state.CONF_ENTITY_ID: 'test.entity', + state.CONF_FROM: 'hello', + automation.CONF_SERVICE: 'test.automation' + } + }) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_fires_on_entity_change_with_to_filter(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'state', + state.CONF_ENTITY_ID: 'test.entity', + state.CONF_TO: 'world', + automation.CONF_SERVICE: 'test.automation' + } + }) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_fires_on_entity_change_with_both_filters(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'state', + state.CONF_ENTITY_ID: 'test.entity', + state.CONF_FROM: 'hello', + state.CONF_TO: 'world', + automation.CONF_SERVICE: 'test.automation' + } + }) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_not_fires_if_to_filter_not_match(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'state', + state.CONF_ENTITY_ID: 'test.entity', + state.CONF_FROM: 'hello', + state.CONF_TO: 'world', + automation.CONF_SERVICE: 'test.automation' + } + }) + + self.hass.states.set('test.entity', 'moon') + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) + + def test_if_not_fires_if_from_filter_not_match(self): + self.hass.states.set('test.entity', 'bye') + + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'state', + state.CONF_ENTITY_ID: 'test.entity', + state.CONF_FROM: 'hello', + state.CONF_TO: 'world', + automation.CONF_SERVICE: 'test.automation' + } + }) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) + + def test_if_not_fires_if_entity_not_match(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'state', + state.CONF_ENTITY_ID: 'test.another_entity', + automation.CONF_SERVICE: 'test.automation' + } + }) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py new file mode 100644 index 00000000000..6dac2708f11 --- /dev/null +++ b/tests/components/automation/test_time.py @@ -0,0 +1,97 @@ +""" +tests.test_component_demo +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests demo component. +""" +import unittest + +import homeassistant as ha +import homeassistant.loader as loader +import homeassistant.util.dt as dt_util +import homeassistant.components.automation as automation +import homeassistant.components.automation.time as time +from homeassistant.const import CONF_PLATFORM + +from tests.common import fire_time_changed + + +class TestAutomationTime(unittest.TestCase): + """ Test the event automation. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = ha.HomeAssistant() + loader.prepare(self.hass) + self.calls = [] + + def record_call(service): + self.calls.append(service) + + self.hass.services.register('test', 'automation', record_call) + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_if_fires_when_hour_matches(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'time', + time.CONF_HOURS: 0, + automation.CONF_SERVICE: 'test.automation' + } + }) + + fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_fires_when_minute_matches(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'time', + time.CONF_MINUTES: 0, + automation.CONF_SERVICE: 'test.automation' + } + }) + + fire_time_changed(self.hass, dt_util.utcnow().replace(minute=0)) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_fires_when_second_matches(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'time', + time.CONF_SECONDS: 0, + automation.CONF_SERVICE: 'test.automation' + } + }) + + fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_fires_when_all_matches(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'time', + time.CONF_HOURS: 0, + time.CONF_MINUTES: 0, + time.CONF_SECONDS: 0, + automation.CONF_SERVICE: 'test.automation' + } + }) + + fire_time_changed(self.hass, dt_util.utcnow().replace( + hour=0, minute=0, second=0)) + + self.hass.states.set('test.entity', 'world') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) From 291cc6238172a1c68b8fb4fa8946ca6e95529ba5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 10 Aug 2015 23:11:46 -0700 Subject: [PATCH 06/33] Add automation mqtt tests --- .../components/automation/__init__.py | 4 +- tests/common.py | 21 ++++- tests/components/automation/test_event.py | 22 +++-- tests/components/automation/test_init.py | 80 ++++++++++++++++++ tests/components/automation/test_mqtt.py | 81 +++++++++++++++++++ tests/components/automation/test_state.py | 38 +++++---- tests/components/automation/test_time.py | 16 ++-- 7 files changed, 228 insertions(+), 34 deletions(-) create mode 100644 tests/components/automation/test_init.py create mode 100644 tests/components/automation/test_mqtt.py diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 23d1c6f02d3..54d16bcca37 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -25,6 +25,7 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): """ Sets up automation. """ + success = False for p_type, p_config in config_per_platform(config, DOMAIN, _LOGGER): platform = prepare_setup_platform(hass, config, DOMAIN, p_type) @@ -36,11 +37,12 @@ def setup(hass, config): if platform.register(hass, p_config, _get_action(hass, p_config)): _LOGGER.info( "Initialized %s rule %s", p_type, p_config.get(CONF_ALIAS, "")) + success = True else: _LOGGER.error( "Error setting up rule %s", p_config.get(CONF_ALIAS, "")) - return True + return success def _get_action(hass, config): diff --git a/tests/common.py b/tests/common.py index bad0723530b..3b3cea6302e 100644 --- a/tests/common.py +++ b/tests/common.py @@ -6,6 +6,7 @@ Helper method for writing tests. """ import os from datetime import timedelta +from unittest import mock import homeassistant as ha import homeassistant.util.dt as dt_util @@ -13,7 +14,7 @@ from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import ( STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED) -from homeassistant.components import sun +from homeassistant.components import sun, mqtt def get_test_config_dir(): @@ -52,6 +53,14 @@ def mock_service(hass, domain, service): return calls +def fire_mqtt_message(hass, topic, payload, qos=0): + hass.bus.fire(mqtt.EVENT_MQTT_MESSAGE_RECEIVED, { + mqtt.ATTR_TOPIC: topic, + mqtt.ATTR_PAYLOAD: payload, + mqtt.ATTR_QOS: qos, + }) + + def fire_time_changed(hass, time): hass.bus.fire(EVENT_TIME_CHANGED, {'now': time}) @@ -93,6 +102,16 @@ def mock_http_component(hass): hass.config.components.append('http') +def mock_mqtt_component(hass): + with mock.patch('homeassistant.components.mqtt.MQTT'): + mqtt.setup(hass, { + mqtt.DOMAIN: { + mqtt.CONF_BROKER: 'mock-broker', + } + }) + hass.config.components.append(mqtt.DOMAIN) + + class MockHTTP(object): """ Mocks the HTTP module. """ diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index 9c8acda86fd..dc685fa944d 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -7,7 +7,6 @@ Tests demo component. import unittest import homeassistant as ha -import homeassistant.loader as loader import homeassistant.components.automation as automation import homeassistant.components.automation.event as event from homeassistant.const import CONF_PLATFORM @@ -18,7 +17,6 @@ class TestAutomationEvent(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name self.hass = ha.HomeAssistant() - loader.prepare(self.hass) self.calls = [] def record_call(service): @@ -30,42 +28,50 @@ class TestAutomationEvent(unittest.TestCase): """ Stop down stuff we started. """ self.hass.stop() + def test_fails_setup_if_no_event_type(self): + self.assertFalse(automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'event', + automation.CONF_SERVICE: 'test.automation' + } + })) + def test_if_fires_on_event(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'event', event.CONF_EVENT_TYPE: 'test_event', automation.CONF_SERVICE: 'test.automation' } - }) + })) self.hass.bus.fire('test_event') self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_event_with_data(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'event', event.CONF_EVENT_TYPE: 'test_event', event.CONF_EVENT_DATA: {'some_attr': 'some_value'}, automation.CONF_SERVICE: 'test.automation' } - }) + })) self.hass.bus.fire('test_event', {'some_attr': 'some_value'}) self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_not_fires_if_event_data_not_matches(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'event', event.CONF_EVENT_TYPE: 'test_event', event.CONF_EVENT_DATA: {'some_attr': 'some_value'}, automation.CONF_SERVICE: 'test.automation' } - }) + })) self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'}) self.hass.pool.block_till_done() diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py new file mode 100644 index 00000000000..4bd0f4b96f3 --- /dev/null +++ b/tests/components/automation/test_init.py @@ -0,0 +1,80 @@ +""" +tests.test_component_demo +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests demo component. +""" +import unittest + +import homeassistant as ha +import homeassistant.components.automation as automation +import homeassistant.components.automation.event as event +from homeassistant.const import CONF_PLATFORM, ATTR_ENTITY_ID + + +class TestAutomationEvent(unittest.TestCase): + """ Test the event automation. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = ha.HomeAssistant() + self.calls = [] + + def record_call(service): + self.calls.append(service) + + self.hass.services.register('test', 'automation', record_call) + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_setup_fails_if_unknown_platform(self): + self.assertFalse(automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'i_do_not_exist' + } + })) + + def test_service_data_not_a_dict(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'event', + event.CONF_EVENT_TYPE: 'test_event', + automation.CONF_SERVICE: 'test.automation', + automation.CONF_SERVICE_DATA: 100 + } + }) + + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_service_specify_data(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'event', + event.CONF_EVENT_TYPE: 'test_event', + automation.CONF_SERVICE: 'test.automation', + automation.CONF_SERVICE_DATA: {'some': 'data'} + } + }) + + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + self.assertEqual('data', self.calls[0].data['some']) + + def test_service_specify_entity_id(self): + automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'event', + event.CONF_EVENT_TYPE: 'test_event', + automation.CONF_SERVICE: 'test.automation', + automation.CONF_SERVICE_ENTITY_ID: 'hello.world' + } + }) + + self.hass.bus.fire('test_event') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + self.assertEqual(['hello.world'], self.calls[0].data[ATTR_ENTITY_ID]) diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py new file mode 100644 index 00000000000..1fdc74bf3ce --- /dev/null +++ b/tests/components/automation/test_mqtt.py @@ -0,0 +1,81 @@ +""" +tests.test_component_demo +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests demo component. +""" +import unittest + +import homeassistant as ha +import homeassistant.components.automation as automation +import homeassistant.components.automation.mqtt as mqtt +from homeassistant.const import CONF_PLATFORM + +from tests.common import mock_mqtt_component, fire_mqtt_message + + +class TestAutomationState(unittest.TestCase): + """ Test the event automation. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = ha.HomeAssistant() + mock_mqtt_component(self.hass) + self.calls = [] + + def record_call(service): + self.calls.append(service) + + self.hass.services.register('test', 'automation', record_call) + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def test_setup_fails_if_no_topic(self): + self.assertFalse(automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'mqtt', + automation.CONF_SERVICE: 'test.automation' + } + })) + + def test_if_fires_on_topic_match(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'mqtt', + mqtt.CONF_TOPIC: 'test-topic', + automation.CONF_SERVICE: 'test.automation' + } + })) + + fire_mqtt_message(self.hass, 'test-topic', '') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_fires_on_topic_and_payload_match(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'mqtt', + mqtt.CONF_TOPIC: 'test-topic', + mqtt.CONF_PAYLOAD: 'hello', + automation.CONF_SERVICE: 'test.automation' + } + })) + + fire_mqtt_message(self.hass, 'test-topic', 'hello') + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + + def test_if_not_fires_on_topic_but_no_payload_match(self): + self.assertTrue(automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'mqtt', + mqtt.CONF_TOPIC: 'test-topic', + mqtt.CONF_PAYLOAD: 'hello', + automation.CONF_SERVICE: 'test.automation' + } + })) + + fire_mqtt_message(self.hass, 'test-topic', 'no-hello') + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index a9c74555b79..5966946b278 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -7,7 +7,6 @@ Tests demo component. import unittest import homeassistant as ha -import homeassistant.loader as loader import homeassistant.components.automation as automation import homeassistant.components.automation.state as state from homeassistant.const import CONF_PLATFORM @@ -18,7 +17,6 @@ class TestAutomationState(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name self.hass = ha.HomeAssistant() - loader.prepare(self.hass) self.hass.states.set('test.entity', 'hello') self.calls = [] @@ -31,49 +29,57 @@ class TestAutomationState(unittest.TestCase): """ Stop down stuff we started. """ self.hass.stop() + def test_setup_fails_if_no_entity_id(self): + self.assertFalse(automation.setup(self.hass, { + automation.DOMAIN: { + CONF_PLATFORM: 'state', + automation.CONF_SERVICE: 'test.automation' + } + })) + def test_if_fires_on_entity_change(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'state', state.CONF_ENTITY_ID: 'test.entity', automation.CONF_SERVICE: 'test.automation' } - }) + })) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_entity_change_with_from_filter(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'state', state.CONF_ENTITY_ID: 'test.entity', state.CONF_FROM: 'hello', automation.CONF_SERVICE: 'test.automation' } - }) + })) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_entity_change_with_to_filter(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'state', state.CONF_ENTITY_ID: 'test.entity', state.CONF_TO: 'world', automation.CONF_SERVICE: 'test.automation' } - }) + })) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_entity_change_with_both_filters(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'state', state.CONF_ENTITY_ID: 'test.entity', @@ -81,14 +87,14 @@ class TestAutomationState(unittest.TestCase): state.CONF_TO: 'world', automation.CONF_SERVICE: 'test.automation' } - }) + })) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_not_fires_if_to_filter_not_match(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'state', state.CONF_ENTITY_ID: 'test.entity', @@ -96,7 +102,7 @@ class TestAutomationState(unittest.TestCase): state.CONF_TO: 'world', automation.CONF_SERVICE: 'test.automation' } - }) + })) self.hass.states.set('test.entity', 'moon') self.hass.pool.block_till_done() @@ -105,7 +111,7 @@ class TestAutomationState(unittest.TestCase): def test_if_not_fires_if_from_filter_not_match(self): self.hass.states.set('test.entity', 'bye') - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'state', state.CONF_ENTITY_ID: 'test.entity', @@ -113,20 +119,20 @@ class TestAutomationState(unittest.TestCase): state.CONF_TO: 'world', automation.CONF_SERVICE: 'test.automation' } - }) + })) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_not_fires_if_entity_not_match(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'state', state.CONF_ENTITY_ID: 'test.another_entity', automation.CONF_SERVICE: 'test.automation' } - }) + })) self.hass.states.set('test.entity', 'world') self.hass.pool.block_till_done() diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index 6dac2708f11..5581cc01348 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -34,13 +34,13 @@ class TestAutomationTime(unittest.TestCase): self.hass.stop() def test_if_fires_when_hour_matches(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'time', time.CONF_HOURS: 0, automation.CONF_SERVICE: 'test.automation' } - }) + })) fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) @@ -49,13 +49,13 @@ class TestAutomationTime(unittest.TestCase): self.assertEqual(1, len(self.calls)) def test_if_fires_when_minute_matches(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'time', time.CONF_MINUTES: 0, automation.CONF_SERVICE: 'test.automation' } - }) + })) fire_time_changed(self.hass, dt_util.utcnow().replace(minute=0)) @@ -64,13 +64,13 @@ class TestAutomationTime(unittest.TestCase): self.assertEqual(1, len(self.calls)) def test_if_fires_when_second_matches(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'time', time.CONF_SECONDS: 0, automation.CONF_SERVICE: 'test.automation' } - }) + })) fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) @@ -79,7 +79,7 @@ class TestAutomationTime(unittest.TestCase): self.assertEqual(1, len(self.calls)) def test_if_fires_when_all_matches(self): - automation.setup(self.hass, { + self.assertTrue(automation.setup(self.hass, { automation.DOMAIN: { CONF_PLATFORM: 'time', time.CONF_HOURS: 0, @@ -87,7 +87,7 @@ class TestAutomationTime(unittest.TestCase): time.CONF_SECONDS: 0, automation.CONF_SERVICE: 'test.automation' } - }) + })) fire_time_changed(self.hass, dt_util.utcnow().replace( hour=0, minute=0, second=0)) From 65a4b3c9f8543ea61d95335c76eb8c642b5a9f0c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 10 Aug 2015 23:11:57 -0700 Subject: [PATCH 07/33] Add MQTT component tests --- .coveragerc | 2 - tests/components/test_mqtt.py | 104 ++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 tests/components/test_mqtt.py diff --git a/.coveragerc b/.coveragerc index 012084af5ce..1dc94450eac 100644 --- a/.coveragerc +++ b/.coveragerc @@ -16,8 +16,6 @@ omit = homeassistant/components/modbus.py homeassistant/components/*/modbus.py - homeassistant/components/mqtt.py - homeassistant/components/wink.py homeassistant/components/*/wink.py diff --git a/tests/components/test_mqtt.py b/tests/components/test_mqtt.py new file mode 100644 index 00000000000..671cae4237f --- /dev/null +++ b/tests/components/test_mqtt.py @@ -0,0 +1,104 @@ +""" +tests.test_component_mqtt +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests MQTT component. +""" +import unittest + +import homeassistant as ha +import homeassistant.components.mqtt as mqtt +from homeassistant.const import EVENT_CALL_SERVICE + +from tests.common import mock_mqtt_component, fire_mqtt_message + + +class TestDemo(unittest.TestCase): + """ Test the demo module. """ + + def setUp(self): # pylint: disable=invalid-name + self.hass = ha.HomeAssistant() + mock_mqtt_component(self.hass) + self.calls = [] + + def tearDown(self): # pylint: disable=invalid-name + """ Stop down stuff we started. """ + self.hass.stop() + + def record_calls(self, *args): + self.calls.append(args) + + def test_publish_calls_service(self): + self.hass.bus.listen_once(EVENT_CALL_SERVICE, self.record_calls) + + mqtt.publish(self.hass, 'test-topic', 'test-payload') + + self.hass.pool.block_till_done() + + self.assertEqual(1, len(self.calls)) + self.assertEqual('test-topic', self.calls[0][0].data[mqtt.ATTR_TOPIC]) + self.assertEqual('test-payload', self.calls[0][0].data[mqtt.ATTR_PAYLOAD]) + + def test_subscribe_topic(self): + mqtt.subscribe(self.hass, 'test-topic', self.record_calls) + + fire_mqtt_message(self.hass, 'test-topic', 'test-payload') + + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + self.assertEqual('test-topic', self.calls[0][0]) + self.assertEqual('test-payload', self.calls[0][1]) + + def test_subscribe_topic_not_match(self): + mqtt.subscribe(self.hass, 'test-topic', self.record_calls) + + fire_mqtt_message(self.hass, 'another-test-topic', 'test-payload') + + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) + + def test_subscribe_topic_level_wildcard(self): + mqtt.subscribe(self.hass, 'test-topic/+/on', self.record_calls) + + fire_mqtt_message(self.hass, 'test-topic/bier/on', 'test-payload') + + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + self.assertEqual('test-topic/bier/on', self.calls[0][0]) + self.assertEqual('test-payload', self.calls[0][1]) + + def test_subscribe_topic_level_wildcard_no_subtree_match(self): + mqtt.subscribe(self.hass, 'test-topic/+/on', self.record_calls) + + fire_mqtt_message(self.hass, 'test-topic/bier', 'test-payload') + + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) + + def test_subscribe_topic_subtree_wildcard_subtree_topic(self): + mqtt.subscribe(self.hass, 'test-topic/#', self.record_calls) + + fire_mqtt_message(self.hass, 'test-topic/bier/on', 'test-payload') + + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + self.assertEqual('test-topic/bier/on', self.calls[0][0]) + self.assertEqual('test-payload', self.calls[0][1]) + + def test_subscribe_topic_subtree_wildcard_root_topic(self): + mqtt.subscribe(self.hass, 'test-topic/#', self.record_calls) + + fire_mqtt_message(self.hass, 'test-topic', 'test-payload') + + self.hass.pool.block_till_done() + self.assertEqual(1, len(self.calls)) + self.assertEqual('test-topic', self.calls[0][0]) + self.assertEqual('test-payload', self.calls[0][1]) + + def test_subscribe_topic_subtree_wildcard_no_match(self): + mqtt.subscribe(self.hass, 'test-topic/#', self.record_calls) + + fire_mqtt_message(self.hass, 'another-test-topic', 'test-payload') + + self.hass.pool.block_till_done() + self.assertEqual(0, len(self.calls)) From 3fad4d8cda8ec961f864f8d7b3587665c00f49f8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 10 Aug 2015 23:34:58 -0700 Subject: [PATCH 08/33] Increase test coverage MQTT --- .coveragerc | 3 +++ homeassistant/__init__.py | 8 +++---- tests/components/test_mqtt.py | 42 +++++++++++++++++++++++++++++++---- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/.coveragerc b/.coveragerc index 1dc94450eac..956bdd023e8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -85,3 +85,6 @@ exclude_lines = # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError + + # The MQTT client is mocked + class MQTT diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index bf21dc0a15d..afc4ea8375f 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -628,12 +628,12 @@ class ServiceRegistry(object): def _execute_service(self, service_and_call): """ Executes a service and fires a SERVICE_EXECUTED event. """ service, call = service_and_call - service(call) - self._bus.fire( - EVENT_SERVICE_EXECUTED, - {ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]}) + if ATTR_SERVICE_CALL_ID in call.data: + self._bus.fire( + EVENT_SERVICE_EXECUTED, + {ATTR_SERVICE_CALL_ID: call.data[ATTR_SERVICE_CALL_ID]}) def _generate_unique_id(self): """ Generates a unique service call id. """ diff --git a/tests/components/test_mqtt.py b/tests/components/test_mqtt.py index 671cae4237f..4c3dbb1d20a 100644 --- a/tests/components/test_mqtt.py +++ b/tests/components/test_mqtt.py @@ -5,19 +5,23 @@ tests.test_component_mqtt Tests MQTT component. """ import unittest +from unittest import mock +import socket -import homeassistant as ha import homeassistant.components.mqtt as mqtt -from homeassistant.const import EVENT_CALL_SERVICE +from homeassistant.const import ( + EVENT_CALL_SERVICE, ATTR_DOMAIN, ATTR_SERVICE, EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP) -from tests.common import mock_mqtt_component, fire_mqtt_message +from tests.common import ( + get_test_home_assistant, mock_mqtt_component, fire_mqtt_message) class TestDemo(unittest.TestCase): """ Test the demo module. """ def setUp(self): # pylint: disable=invalid-name - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant(1) mock_mqtt_component(self.hass) self.calls = [] @@ -28,6 +32,28 @@ class TestDemo(unittest.TestCase): def record_calls(self, *args): self.calls.append(args) + def test_client_starts_on_home_assistant_start(self): + self.hass.bus.fire(EVENT_HOMEASSISTANT_START) + self.hass.pool.block_till_done() + self.assertTrue(mqtt.MQTT_CLIENT.start.called) + + def test_client_stops_on_home_assistant_start(self): + self.hass.bus.fire(EVENT_HOMEASSISTANT_START) + self.hass.pool.block_till_done() + self.hass.bus.fire(EVENT_HOMEASSISTANT_STOP) + self.hass.pool.block_till_done() + self.assertTrue(mqtt.MQTT_CLIENT.stop.called) + + def test_setup_fails_if_no_broker_config(self): + self.assertFalse(mqtt.setup(self.hass, {mqtt.DOMAIN: {}})) + + def test_setup_fails_if_no_connect_broker(self): + with mock.patch('homeassistant.components.mqtt.MQTT', + side_effect=socket.error()): + self.assertFalse(mqtt.setup(self.hass, {mqtt.DOMAIN: { + mqtt.CONF_BROKER: 'test-broker', + }})) + def test_publish_calls_service(self): self.hass.bus.listen_once(EVENT_CALL_SERVICE, self.record_calls) @@ -39,6 +65,14 @@ class TestDemo(unittest.TestCase): self.assertEqual('test-topic', self.calls[0][0].data[mqtt.ATTR_TOPIC]) self.assertEqual('test-payload', self.calls[0][0].data[mqtt.ATTR_PAYLOAD]) + def test_service_call_without_topic_does_not_publush(self): + self.hass.bus.fire(EVENT_CALL_SERVICE, { + ATTR_DOMAIN: mqtt.DOMAIN, + ATTR_SERVICE: mqtt.SERVICE_PUBLISH + }) + self.hass.pool.block_till_done() + self.assertTrue(not mqtt.MQTT_CLIENT.publish.called) + def test_subscribe_topic(self): mqtt.subscribe(self.hass, 'test-topic', self.record_calls) From 92fc7eab36dfc7823c7ea03fbff363d8f8c20c96 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Tue, 11 Aug 2015 09:28:07 +0200 Subject: [PATCH 09/33] added component and sensor --- .gitmodules | 3 + homeassistant/components/sensor/verisure.py | 65 +++++++++++++++++++++ homeassistant/components/verisure.py | 57 ++++++++++++++++++ requirements.txt | 2 + 4 files changed, 127 insertions(+) create mode 100644 homeassistant/components/sensor/verisure.py create mode 100644 homeassistant/components/verisure.py diff --git a/.gitmodules b/.gitmodules index a627e522d8f..2446411c57c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "homeassistant/components/frontend/www_static/home-assistant-polymer"] path = homeassistant/components/frontend/www_static/home-assistant-polymer url = https://github.com/balloob/home-assistant-polymer.git +[submodule "homeassistant/external/verisure"] + path = homeassistant/external/verisure + url = https://github.com/persandstrom/python-verisure.git diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/sensor/verisure.py new file mode 100644 index 00000000000..6f4f8054772 --- /dev/null +++ b/homeassistant/components/sensor/verisure.py @@ -0,0 +1,65 @@ +""" +homeassistant.components.sensor.verisure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Interfaces with Verisure sensors. +""" +import logging + +import homeassistant.components.verisure as verisure + +from homeassistant.helpers.entity import Entity +from homeassistant.const import STATE_OPEN, STATE_CLOSED + +_LOGGER = logging.getLogger(__name__) + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Verisure platform. """ + + if not verisure.MY_PAGES: + _LOGGER.error('A connection has not been made to Verisure mypages.') + return False + + sensors = [ + VerisureClimateDevice(status) for status + in verisure.MY_PAGES.get_climate_status()] + + sensors.extend([ + VerisureAlarmDevice(status) for status + in verisure.MY_PAGES.get_alarm_status()]) + + + add_devices(sensors) + + +class VerisureClimateDevice(Entity): + """ represents a Verisure climate sensor within home assistant. """ + + def __init__(self, climate_status): + self._status = climate_status + + @property + def name(self): + """ Returns the name of the device. """ + return self._status.location + + @property + def state(self): + """ Returns the state of the device. """ + return self._status.temperature + + +class VerisureAlarmDevice(Entity): + """ represents a Verisure alarm remote control within home assistant. """ + + def __init__(self, alarm_status): + self._status = alarm_status + + @property + def name(self): + """ Returns the name of the device. """ + return 'Alarm {}'.format(self._status.id) + + @property + def state(self): + """ Returns the state of the device. """ + return self._status.status diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py new file mode 100644 index 00000000000..77a34ad979d --- /dev/null +++ b/homeassistant/components/verisure.py @@ -0,0 +1,57 @@ +""" +components.verisure +~~~~~~~~~~~~~~~~~~ +""" +import logging + +from homeassistant import bootstrap +from homeassistant.helpers import validate_config +from homeassistant.loader import get_component +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + CONF_USERNAME, CONF_PASSWORD, + EVENT_PLATFORM_DISCOVERED, + ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME) + +DOMAIN = "verisure" +DEPENDENCIES = [] +REQUIREMENTS = ['https://github.com/persandstrom/python-verisure/archive/master.zip'] + +MY_PAGES = None +_LOGGER = logging.getLogger(__name__) + +DISCOVER_SENSORS = "wink.sensors" + +def setup(hass, config): + """ Setup the Verisure component. """ + + if not validate_config(config, + {DOMAIN: [CONF_USERNAME, CONF_PASSWORD]}, + _LOGGER): + return False + + from verisure import MyPages + global MY_PAGES + MY_PAGES = MyPages(config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD]) + MY_PAGES.login() + + component = get_component('sensor') + bootstrap.setup_component(hass, component.DOMAIN, config) + + # Fire discovery event + hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { + ATTR_SERVICE: DISCOVER_SENSORS, + ATTR_DISCOVERED: {} + }) + + def stop_verisure(event): + """ Stop the Arduino service. """ + MY_PAGES.logout() + + def start_verisure(event): + """ Start the Arduino service. """ + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_verisure) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_verisure) + + return True diff --git a/requirements.txt b/requirements.txt index 445a2fe3657..24027be2d3b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -110,3 +110,5 @@ paho-mqtt>=1.1 # PyModbus (modbus) https://github.com/bashwork/pymodbus/archive/python3.zip#pymodbus>=1.2.0 +# Verisure +https://github.com/persandstrom/python-verisure/archive/master.zip From 2a9616e88cb23bf1cdb26f4a3ba5194ba4ada1f0 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Aug 2015 14:38:06 +0200 Subject: [PATCH 10/33] add new entries --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 00676577281..c9d3045cbb4 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ Check out [the website](https://home-assistant.io) for installation instructions Examples of devices it can interface it: - * Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/) - * [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors - * [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/) and [Kodi (XBMC)](http://kodi.tv/) - * Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), and [Modbus](http://www.modbus.org/) - * Integrate data from the [Bitcoin](https://bitcoin.org) network, local meteorological data from [OpenWeatherMap](http://openweathermap.org/), [Transmission](http://www.transmissionbt.com/) or [SABnzbd](http://sabnzbd.org). + * Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), and [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/) + * [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, [Efergy](https://efergy.com) plugs, [Edimax](http://www.edimax.com/) switches, RFXtrx sensors, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors + * [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/), [Logitech Squeezebox](https://en.wikipedia.org/wiki/Squeezebox_%28network_music_player%29), and [Kodi (XBMC)](http://kodi.tv/) + * Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), [Arduino](https://www.arduino.cc/), [Raspberry Pi](https://www.raspberrypi.org/), and [Modbus](http://www.modbus.org/) + * Integrate data from the [Bitcoin](https://bitcoin.org) network, meteorological data from [OpenWeatherMap](http://openweathermap.org/) and [Forecast.io](https://forecast.io/), [Transmission](http://www.transmissionbt.com/), or [SABnzbd](http://sabnzbd.org). * [See full list of supported devices](https://home-assistant.io/components/) Built home automation on top of your devices: From 27ca6116899b6461edaf33dd40dc052fe861a243 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 11 Aug 2015 07:47:47 -0700 Subject: [PATCH 11/33] Fix MQTT coverage directives --- .coveragerc | 3 --- homeassistant/components/mqtt.py | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.coveragerc b/.coveragerc index 956bdd023e8..1dc94450eac 100644 --- a/.coveragerc +++ b/.coveragerc @@ -85,6 +85,3 @@ exclude_lines = # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError - - # The MQTT client is mocked - class MQTT diff --git a/homeassistant/components/mqtt.py b/homeassistant/components/mqtt.py index 98612e2bd13..a892cc5015c 100644 --- a/homeassistant/components/mqtt.py +++ b/homeassistant/components/mqtt.py @@ -155,7 +155,7 @@ def setup(hass, config): # This is based on one of the paho-mqtt examples: # http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.python.git/tree/examples/sub-class.py # pylint: disable=too-many-arguments -class MQTT(object): +class MQTT(object): # pragma: no cover """ Implements messaging service for MQTT. """ def __init__(self, hass, broker, port, client_id, keepalive, username, password): @@ -237,7 +237,7 @@ class MQTT(object): }) -def _raise_on_error(result): +def _raise_on_error(result): # pragma: no cover """ Raise error if error result. """ if result != 0: raise HomeAssistantError('Error talking to MQTT: {}'.format(result)) From 60abaa585c9f0004c7a0a60790036c4e8f9c44b8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 11 Aug 2015 08:20:13 -0700 Subject: [PATCH 12/33] Add test for bootstrap --- tests/common.py | 18 ++++++++++++++++++ tests/test_bootstrap.py | 41 +++++++++++++++++++++++++++++++++++++++++ tests/test_config.py | 20 +------------------- 3 files changed, 60 insertions(+), 19 deletions(-) create mode 100644 tests/test_bootstrap.py diff --git a/tests/common.py b/tests/common.py index 3b3cea6302e..d6dd6cd3279 100644 --- a/tests/common.py +++ b/tests/common.py @@ -9,6 +9,7 @@ from datetime import timedelta from unittest import mock import homeassistant as ha +import homeassistant.util.location as location_util import homeassistant.util.dt as dt_util from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import ( @@ -40,6 +41,23 @@ def get_test_home_assistant(num_threads=None): return hass +def mock_detect_location_info(): + """ Mock implementation of util.detect_location_info. """ + return location_util.LocationInfo( + ip='1.1.1.1', + country_code='US', + country_name='United States', + region_code='CA', + region_name='California', + city='San Diego', + zip_code='92122', + time_zone='America/Los_Angeles', + latitude='2.0', + longitude='1.0', + use_fahrenheit=True, + ) + + def mock_service(hass, domain, service): """ Sets up a fake service. diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py new file mode 100644 index 00000000000..df05964a79f --- /dev/null +++ b/tests/test_bootstrap.py @@ -0,0 +1,41 @@ +""" +tests.test_bootstrap +~~~~~~~~~~~~~~~~~~~~ + +Tests bootstrap. +""" +# pylint: disable=too-many-public-methods,protected-access +import tempfile +import unittest +from unittest import mock + +from homeassistant import bootstrap +import homeassistant.util.dt as dt_util + +from tests.common import mock_detect_location_info + + +class TestBootstrap(unittest.TestCase): + """ Test the bootstrap utils. """ + + def setUp(self): + self.orig_timezone = dt_util.DEFAULT_TIME_ZONE + + def tearDown(self): + dt_util.DEFAULT_TIME_ZONE = self.orig_timezone + + def test_from_config_file(self): + components = ['browser', 'conversation', 'script'] + with tempfile.NamedTemporaryFile() as fp: + for comp in components: + fp.write('{}:\n'.format(comp).encode('utf-8')) + fp.flush() + + with mock.patch('homeassistant.util.location.detect_location_info', + mock_detect_location_info): + hass = bootstrap.from_config_file(fp.name) + + components.append('group') + + self.assertEqual(sorted(components), + sorted(hass.config.components)) diff --git a/tests/test_config.py b/tests/test_config.py index 235549f6cef..b8d050d14ae 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -10,13 +10,12 @@ import unittest.mock as mock import os from homeassistant import DOMAIN, HomeAssistantError -import homeassistant.util.location as location_util import homeassistant.config as config_util from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE) -from common import get_test_config_dir +from common import get_test_config_dir, mock_detect_location_info CONFIG_DIR = get_test_config_dir() YAML_PATH = os.path.join(CONFIG_DIR, config_util.YAML_CONFIG_FILE) @@ -28,23 +27,6 @@ def create_file(path): pass -def mock_detect_location_info(): - """ Mock implementation of util.detect_location_info. """ - return location_util.LocationInfo( - ip='1.1.1.1', - country_code='US', - country_name='United States', - region_code='CA', - region_name='California', - city='San Diego', - zip_code='92122', - time_zone='America/Los_Angeles', - latitude='2.0', - longitude='1.0', - use_fahrenheit=True, - ) - - class TestConfig(unittest.TestCase): """ Test the config utils. """ From bd373a4d25b5b4a8e4936937d0426d00bce89b69 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Aug 2015 14:52:53 +0200 Subject: [PATCH 13/33] update header --- homeassistant/components/light/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 9aedb98085c..d2f8033add7 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -1,6 +1,6 @@ """ homeassistant.components.light -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Provides functionality to interact with lights. From c900836410bead282a196be249a81d47b0c58f1b Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Aug 2015 14:53:55 +0200 Subject: [PATCH 14/33] update header and docstring --- homeassistant/components/light/wink.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/light/wink.py index a703823ae22..e8c8eb7a224 100644 --- a/homeassistant/components/light/wink.py +++ b/homeassistant/components/light/wink.py @@ -1,4 +1,8 @@ -""" Support for Wink lights. """ +""" +homeassistant.components.light.wink +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for Wink lights. +""" import logging from homeassistant.components.light import ATTR_BRIGHTNESS @@ -29,7 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class WinkLight(WinkToggleDevice): - """ Represents a Wink light """ + """ Represents a Wink light. """ # pylint: disable=too-few-public-methods def turn_on(self, **kwargs): From 2289d3e826b63e4b51ff7b20060393d9a98cecc5 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Aug 2015 14:54:23 +0200 Subject: [PATCH 15/33] upadte header --- homeassistant/components/light/vera.py | 32 ++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/light/vera.py b/homeassistant/components/light/vera.py index fe363923c76..f25c110cc46 100644 --- a/homeassistant/components/light/vera.py +++ b/homeassistant/components/light/vera.py @@ -1,13 +1,15 @@ """ -Support for Vera lights. +homeassistant.components.light.vera +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Support for Vera lights. This component is useful if you wish for switches +connected to your Vera controller to appear as lights in Home Assistant. +All switches will be added as a light unless you exclude them in the config. Configuration: -This component is useful if you wish for switches connected to your Vera -controller to appear as lights in homeassistant. All switches will be added -as a light unless you exclude them in the config. To use the Vera lights you will need to add something like the following to -your config/configuration.yaml +your config/configuration.yaml. light: platform: vera @@ -19,22 +21,19 @@ light: 13: name: Another switch -VARIABLES: +Variables: vera_controller_url *Required This is the base URL of your vera controller including the port number if not -running on 80 -Example: http://192.168.1.21:3480/ - +running on 80. Example: http://192.168.1.21:3480/ device_data *Optional -This contains an array additional device info for your Vera devices. It is not +This contains an array additional device info for your Vera devices. It is not required and if not specified all lights configured in your Vera controller -will be added with default values. You should use the id of your vera device -as the key for the device within device_data - +will be added with default values. You should use the id of your vera device +as the key for the device within device_data. These are the variables for the device_data array: @@ -42,13 +41,12 @@ name *Optional This parameter allows you to override the name of your Vera device in the HA interface, if not specified the value configured for the device in your Vera -will be used - +will be used. exclude *Optional -This parameter allows you to exclude the specified device from homeassistant, -it should be set to "true" if you want this device excluded +This parameter allows you to exclude the specified device from Home Assistant, +it should be set to "true" if you want this device excluded. """ import logging From 06a40ad30cf10725ba478f2955ec047974be48c0 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Aug 2015 14:55:30 +0200 Subject: [PATCH 16/33] update header --- homeassistant/components/light/hue.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index c908992eb82..f012d160b7f 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -1,4 +1,8 @@ -""" Support for Hue lights. """ +""" +homeassistant.components.light.hue +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for Hue lights. +""" import logging import socket from datetime import timedelta From bb848e7fcda246f772dc528ce0748d396748853a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Aug 2015 14:55:47 +0200 Subject: [PATCH 17/33] update header --- homeassistant/components/light/isy994.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/light/isy994.py b/homeassistant/components/light/isy994.py index ae0225a1e3c..b231fe3e441 100644 --- a/homeassistant/components/light/isy994.py +++ b/homeassistant/components/light/isy994.py @@ -1,8 +1,10 @@ -""" Support for ISY994 lights. """ -# system imports +""" +homeassistant.components.light.isy994 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for ISY994 lights. +""" import logging -# homeassistant imports from homeassistant.components.isy994 import (ISYDeviceABC, ISY, SENSOR_STRING, HIDDEN_STRING) from homeassistant.components.light import ATTR_BRIGHTNESS @@ -10,7 +12,7 @@ from homeassistant.const import STATE_ON, STATE_OFF def setup_platform(hass, config, add_devices, discovery_info=None): - """ Sets up the isy994 platform. """ + """ Sets up the ISY994 platform. """ logger = logging.getLogger(__name__) devs = [] # verify connection @@ -29,7 +31,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ISYLightDevice(ISYDeviceABC): - """ represents as isy light within home assistant. """ + """ Represents as ISY light. """ _domain = 'light' _dtype = 'analog' From 19b62c1088c8c554b91735756ec8b04c4d7301f9 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Aug 2015 14:56:20 +0200 Subject: [PATCH 18/33] update header and docstrings --- homeassistant/components/light/tellstick.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/light/tellstick.py b/homeassistant/components/light/tellstick.py index bf74e7a30a9..9132604b294 100644 --- a/homeassistant/components/light/tellstick.py +++ b/homeassistant/components/light/tellstick.py @@ -1,4 +1,8 @@ -""" Support for Tellstick lights. """ +""" +homeassistant.components.light.tellstick +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for Tellstick lights. +""" import logging # pylint: disable=no-name-in-module, import-error from homeassistant.components.light import Light, ATTR_BRIGHTNESS @@ -9,7 +13,7 @@ REQUIREMENTS = ['tellcore-py>=1.0.4'] def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return tellstick lights. """ + """ Find and return Tellstick lights. """ try: import tellcore.telldus as telldus @@ -29,7 +33,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): class TellstickLight(Light): - """ Represents a tellstick light """ + """ Represents a Tellstick light. """ last_sent_command_mask = (tellcore_constants.TELLSTICK_TURNON | tellcore_constants.TELLSTICK_TURNOFF | tellcore_constants.TELLSTICK_DIM | From d0fc91d84acae136fbb721314479863f2317023f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 11 Aug 2015 14:56:56 +0200 Subject: [PATCH 19/33] update header --- .../components/light/limitlessled.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/light/limitlessled.py b/homeassistant/components/light/limitlessled.py index e4eb588688c..b3e0858ffe2 100644 --- a/homeassistant/components/light/limitlessled.py +++ b/homeassistant/components/light/limitlessled.py @@ -1,16 +1,21 @@ """ homeassistant.components.light.limitlessled -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for LimitlessLED bulbs, also known as... -EasyBulb -AppLight -AppLamp -MiLight -LEDme -dekolight -iLight +- EasyBulb +- AppLight +- AppLamp +- MiLight +- LEDme +- dekolight +- iLight + +Configuration: + +To use limitlessled you will need to add the following to your +config/configuration.yaml. light: platform: limitlessled From c7ca6e478426c3578c26d976b337881a3c4761ad Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Wed, 12 Aug 2015 13:00:47 +0200 Subject: [PATCH 20/33] Added a switch --- homeassistant/components/sensor/verisure.py | 52 ++++++++++----- homeassistant/components/switch/verisure.py | 67 +++++++++++++++++++ homeassistant/components/verisure.py | 73 +++++++++++++++------ 3 files changed, 156 insertions(+), 36 deletions(-) create mode 100644 homeassistant/components/switch/verisure.py diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/sensor/verisure.py index 6f4f8054772..12ca073ceab 100644 --- a/homeassistant/components/sensor/verisure.py +++ b/homeassistant/components/sensor/verisure.py @@ -8,10 +8,13 @@ import logging import homeassistant.components.verisure as verisure from homeassistant.helpers.entity import Entity -from homeassistant.const import STATE_OPEN, STATE_CLOSED +from homeassistant.const import TEMP_CELCIUS _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['verisure'] + + def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the Verisure platform. """ @@ -19,14 +22,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error('A connection has not been made to Verisure mypages.') return False - sensors = [ - VerisureClimateDevice(status) for status - in verisure.MY_PAGES.get_climate_status()] + sensors = [] sensors.extend([ - VerisureAlarmDevice(status) for status - in verisure.MY_PAGES.get_alarm_status()]) + VerisureClimateDevice(value) + for value in verisure.get_climate_status().values() + ]) + sensors.extend([ + VerisureAlarmDevice(value) + for value in verisure.get_alarm_status().values() + ]) add_devices(sensors) @@ -35,31 +41,45 @@ class VerisureClimateDevice(Entity): """ represents a Verisure climate sensor within home assistant. """ def __init__(self, climate_status): - self._status = climate_status - + self._id = climate_status.id + self._device = verisure.MY_PAGES.DEVICE_CLIMATE + @property def name(self): """ Returns the name of the device. """ - return self._status.location + return verisure.STATUS[self._device][self._id].location @property def state(self): """ Returns the state of the device. """ - return self._status.temperature + # remove ° character + return verisure.STATUS[self._device][self._id].temperature[:-1] + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity """ + return TEMP_CELCIUS # can verisure report in fahrenheit? + + def update(self): + verisure.update() class VerisureAlarmDevice(Entity): - """ represents a Verisure alarm remote control within home assistant. """ - + """ represents a Verisure alarm status within home assistant. """ + def __init__(self, alarm_status): - self._status = alarm_status - + self._id = alarm_status.id + self._device = verisure.MY_PAGES.DEVICE_ALARM + @property def name(self): """ Returns the name of the device. """ - return 'Alarm {}'.format(self._status.id) + return 'Alarm {}'.format(self._id) @property def state(self): """ Returns the state of the device. """ - return self._status.status + return verisure.STATUS[self._device][self._id].status + + def update(self): + verisure.update() diff --git a/homeassistant/components/switch/verisure.py b/homeassistant/components/switch/verisure.py new file mode 100644 index 00000000000..98979b400ed --- /dev/null +++ b/homeassistant/components/switch/verisure.py @@ -0,0 +1,67 @@ +""" +homeassistant.components.switch.verisure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Support for Verisure Smartplugs + +Configuration: + +switch: + platform: verisure + +Variables: + +""" +import logging + +import homeassistant.components.verisure as verisure +from homeassistant.components.switch import SwitchDevice + +DEPENDENCIES = ['verisure'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Arduino platform. """ + + if not verisure.MY_PAGES: + _LOGGER.error('A connection has not been made to Verisure mypages.') + return False + + switches = [] + + switches.extend([ + VerisureSmartplug(value) + for value in verisure.get_smartplug_status().values() + ]) + + add_devices(switches) + + +class VerisureSmartplug(SwitchDevice): + """ Represents a Verisure smartplug. """ + def __init__(self, smartplug_status): + self._id = smartplug_status.id + + @property + def name(self): + """ Get the name (location) of the smartplug. """ + return verisure.get_smartplug_status()[self._id].location + + @property + def is_on(self): + """ Returns True if on """ + plug_status = verisure.get_smartplug_status()[self._id].status + return plug_status == verisure.MY_PAGES.SMARTPLUG_ON + + def turn_on(self): + """ Set smartplug status on """ + verisure.MY_PAGES.set_smartplug_status( + self._id, + verisure.MY_PAGES.SMARTPLUG_ON) + + def turn_off(self): + """ Set smartplug status off. """ + verisure.MY_PAGES.set_smartplug_status( + self._id, + verisure.MY_PAGES.SMARTPLUG_OFF) diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index 77a34ad979d..f902381fbb9 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -3,24 +3,27 @@ components.verisure ~~~~~~~~~~~~~~~~~~ """ import logging +from datetime import timedelta -from homeassistant import bootstrap from homeassistant.helpers import validate_config -from homeassistant.loader import get_component +from homeassistant.util import Throttle from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - CONF_USERNAME, CONF_PASSWORD, - EVENT_PLATFORM_DISCOVERED, - ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME) + CONF_USERNAME, CONF_PASSWORD) DOMAIN = "verisure" DEPENDENCIES = [] -REQUIREMENTS = ['https://github.com/persandstrom/python-verisure/archive/master.zip'] +REQUIREMENTS = [ + 'https://github.com/persandstrom/python-verisure/archive/master.zip' + ] -MY_PAGES = None _LOGGER = logging.getLogger(__name__) -DISCOVER_SENSORS = "wink.sensors" +MY_PAGES = None +STATUS = {} + +MIN_TIME_BETWEEN_REQUESTS = timedelta(seconds=5) + def setup(hass, config): """ Setup the Verisure component. """ @@ -31,27 +34,57 @@ def setup(hass, config): return False from verisure import MyPages + + STATUS[MyPages.DEVICE_ALARM] = {} + STATUS[MyPages.DEVICE_CLIMATE] = {} + STATUS[MyPages.DEVICE_SMARTPLUG] = {} + global MY_PAGES - MY_PAGES = MyPages(config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD]) + MY_PAGES = MyPages( + config[DOMAIN][CONF_USERNAME], + config[DOMAIN][CONF_PASSWORD]) MY_PAGES.login() - - component = get_component('sensor') - bootstrap.setup_component(hass, component.DOMAIN, config) - - # Fire discovery event - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { - ATTR_SERVICE: DISCOVER_SENSORS, - ATTR_DISCOVERED: {} - }) + update() def stop_verisure(event): - """ Stop the Arduino service. """ + """ Stop the Verisure service. """ MY_PAGES.logout() def start_verisure(event): - """ Start the Arduino service. """ + """ Start the Verisure service. """ hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_verisure) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_verisure) return True + + +def get_alarm_status(): + ''' return a list of status overviews for alarm components ''' + return STATUS[MY_PAGES.DEVICE_ALARM] + + +def get_climate_status(): + ''' return a list of status overviews for alarm components ''' + return STATUS[MY_PAGES.DEVICE_CLIMATE] + + +def get_smartplug_status(): + ''' return a list of status overviews for alarm components ''' + return STATUS[MY_PAGES.DEVICE_SMARTPLUG] + + +@Throttle(MIN_TIME_BETWEEN_REQUESTS) +def update(): + ''' Updates the status of verisure components ''' + try: + for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_ALARM): + STATUS[MY_PAGES.DEVICE_ALARM][overview.id] = overview + for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_CLIMATE): + STATUS[MY_PAGES.DEVICE_CLIMATE][overview.id] = overview + for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_SMARTPLUG): + STATUS[MY_PAGES.DEVICE_SMARTPLUG][overview.id] = overview + except ConnectionError as ex: + _LOGGER.error('Caught connection error {}, tries to reconnect'.format( + ex)) + MY_PAGES.login() From 1b59859681151cd85893ff3d1be48a99dddc3be2 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Wed, 12 Aug 2015 13:28:22 +0200 Subject: [PATCH 21/33] fixed pylint warning --- homeassistant/components/verisure.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index f902381fbb9..6776766d20e 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -85,6 +85,5 @@ def update(): for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_SMARTPLUG): STATUS[MY_PAGES.DEVICE_SMARTPLUG][overview.id] = overview except ConnectionError as ex: - _LOGGER.error('Caught connection error {}, tries to reconnect'.format( - ex)) + _LOGGER.error('Caught connection error %, tries to reconnect', ex) MY_PAGES.login() From f20be1e7f88fce0f254f5d63e7eb06e9c584ab8a Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Wed, 12 Aug 2015 13:32:15 +0200 Subject: [PATCH 22/33] fixed pylint warning --- homeassistant/components/verisure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index 6776766d20e..a2bdf688175 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -85,5 +85,5 @@ def update(): for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_SMARTPLUG): STATUS[MY_PAGES.DEVICE_SMARTPLUG][overview.id] = overview except ConnectionError as ex: - _LOGGER.error('Caught connection error %, tries to reconnect', ex) + _LOGGER.error('Caught connection error %s, tries to reconnect', ex) MY_PAGES.login() From ad327b64eddeb2abd31ab13e53c9e2bb967588fa Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Sat, 15 Aug 2015 13:36:30 +0200 Subject: [PATCH 23/33] code reveiw --- .gitmodules | 3 - homeassistant/components/sensor/__init__.py | 5 +- homeassistant/components/switch/__init__.py | 3 +- homeassistant/components/switch/verisure.py | 11 +++- homeassistant/components/verisure.py | 63 +++++++++++++++++---- 5 files changed, 64 insertions(+), 21 deletions(-) diff --git a/.gitmodules b/.gitmodules index 2446411c57c..a627e522d8f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,3 @@ [submodule "homeassistant/components/frontend/www_static/home-assistant-polymer"] path = homeassistant/components/frontend/www_static/home-assistant-polymer url = https://github.com/balloob/home-assistant-polymer.git -[submodule "homeassistant/external/verisure"] - path = homeassistant/external/verisure - url = https://github.com/persandstrom/python-verisure.git diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 5cbd07d0e59..90317cdf90a 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -6,7 +6,7 @@ Component to interface with various sensors that can be monitored. import logging from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.components import wink, zwave, isy994 +from homeassistant.components import wink, zwave, isy994, verisure DOMAIN = 'sensor' DEPENDENCIES = [] @@ -18,7 +18,8 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}' DISCOVERY_PLATFORMS = { wink.DISCOVER_SENSORS: 'wink', zwave.DISCOVER_SENSORS: 'zwave', - isy994.DISCOVER_SENSORS: 'isy994' + isy994.DISCOVER_SENSORS: 'isy994', + verisure.DISCOVER_SENSORS: 'verisure' } diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 1d6f0b79d52..424d4505d39 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -11,7 +11,7 @@ from homeassistant.helpers.entity import ToggleEntity from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) -from homeassistant.components import group, discovery, wink, isy994 +from homeassistant.components import group, discovery, wink, isy994, verisure DOMAIN = 'switch' DEPENDENCIES = [] @@ -32,6 +32,7 @@ DISCOVERY_PLATFORMS = { discovery.SERVICE_WEMO: 'wemo', wink.DISCOVER_SWITCHES: 'wink', isy994.DISCOVER_SWITCHES: 'isy994', + verisure.DISCOVER_SWITCHES: 'verisure' } PROP_TO_ATTR = { diff --git a/homeassistant/components/switch/verisure.py b/homeassistant/components/switch/verisure.py index 98979b400ed..52182bf94a9 100644 --- a/homeassistant/components/switch/verisure.py +++ b/homeassistant/components/switch/verisure.py @@ -42,6 +42,8 @@ class VerisureSmartplug(SwitchDevice): """ Represents a Verisure smartplug. """ def __init__(self, smartplug_status): self._id = smartplug_status.id + self.STATUS_ON = verisure.MY_PAGES.SMARTPLUG_ON + self.STATUS_OFF = verisure.MY_PAGES.SMARTPLUG_OFF @property def name(self): @@ -52,16 +54,19 @@ class VerisureSmartplug(SwitchDevice): def is_on(self): """ Returns True if on """ plug_status = verisure.get_smartplug_status()[self._id].status - return plug_status == verisure.MY_PAGES.SMARTPLUG_ON + return plug_status == self.STATUS_ON def turn_on(self): """ Set smartplug status on """ verisure.MY_PAGES.set_smartplug_status( self._id, - verisure.MY_PAGES.SMARTPLUG_ON) + self.STATUS_ON) def turn_off(self): """ Set smartplug status off. """ verisure.MY_PAGES.set_smartplug_status( self._id, - verisure.MY_PAGES.SMARTPLUG_OFF) + self.STATUS_OFF) + + def update(self): + verisure.update() diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index a2bdf688175..6e0d6a81539 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -5,13 +5,21 @@ components.verisure import logging from datetime import timedelta +from homeassistant import bootstrap +from homeassistant.loader import get_component + from homeassistant.helpers import validate_config from homeassistant.util import Throttle from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + EVENT_PLATFORM_DISCOVERED, + ATTR_SERVICE, ATTR_DISCOVERED, CONF_USERNAME, CONF_PASSWORD) + DOMAIN = "verisure" +DISCOVER_SENSORS = 'verisure.sensors' +DISCOVER_SWITCHES = 'verisure.switches' + DEPENDENCIES = [] REQUIREMENTS = [ 'https://github.com/persandstrom/python-verisure/archive/master.zip' @@ -22,6 +30,12 @@ _LOGGER = logging.getLogger(__name__) MY_PAGES = None STATUS = {} +VERISURE_LOGIN_ERROR = None +VERISURE_ERROR = None + +# if wrong password was given don't try again +WRONG_PASSWORD_GIVEN = False + MIN_TIME_BETWEEN_REQUESTS = timedelta(seconds=5) @@ -33,7 +47,7 @@ def setup(hass, config): _LOGGER): return False - from verisure import MyPages + from verisure import MyPages, LoginError, Error STATUS[MyPages.DEVICE_ALARM] = {} STATUS[MyPages.DEVICE_CLIMATE] = {} @@ -43,18 +57,27 @@ def setup(hass, config): MY_PAGES = MyPages( config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD]) - MY_PAGES.login() + global VERISURE_LOGIN_ERROR, VERISURE_ERROR + VERISURE_LOGIN_ERROR = LoginError + VERISURE_ERROR = Error + + try: + MY_PAGES.login() + except (ConnectionError, Error) as ex: + _LOGGER.error('Could not log in to verisure mypages, %s', ex.message) + return False + update() - def stop_verisure(event): - """ Stop the Verisure service. """ - MY_PAGES.logout() + # Load components for the devices in the ISY controller that we support + for comp_name, discovery in ((('sensor', DISCOVER_SENSORS), + ('switch', DISCOVER_SWITCHES))): + component = get_component(comp_name) + bootstrap.setup_component(hass, component.DOMAIN, config) - def start_verisure(event): - """ Start the Verisure service. """ - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_verisure) - - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_verisure) + hass.bus.fire(EVENT_PLATFORM_DISCOVERED, + {ATTR_SERVICE: discovery, + ATTR_DISCOVERED: {}}) return True @@ -74,9 +97,25 @@ def get_smartplug_status(): return STATUS[MY_PAGES.DEVICE_SMARTPLUG] +def reconnect(): + ''' reconnect to verisure mypages ''' + try: + MY_PAGES.login() + except VERISURE_LOGIN_ERROR as ex: + _LOGGER.error("Could not login to Verisure mypages, %s", ex.message) + global WRONG_PASSWORD_GIVEN + WRONG_PASSWORD_GIVEN = True + except (ConnectionError, VERISURE_ERROR) as ex: + _LOGGER.error("Could not login to Verisure mypages, %s", ex.message) + + @Throttle(MIN_TIME_BETWEEN_REQUESTS) def update(): ''' Updates the status of verisure components ''' + if WRONG_PASSWORD_GIVEN: + # Is there any way to inform user? + return + try: for overview in MY_PAGES.get_overview(MY_PAGES.DEVICE_ALARM): STATUS[MY_PAGES.DEVICE_ALARM][overview.id] = overview @@ -86,4 +125,4 @@ def update(): STATUS[MY_PAGES.DEVICE_SMARTPLUG][overview.id] = overview except ConnectionError as ex: _LOGGER.error('Caught connection error %s, tries to reconnect', ex) - MY_PAGES.login() + reconnect() From a0f2f3814b22fd792eeb1d84d252f81abac1d32a Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Sun, 16 Aug 2015 06:51:09 +0200 Subject: [PATCH 24/33] fixed flak8 --- homeassistant/components/switch/verisure.py | 10 +++++----- homeassistant/components/verisure.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/switch/verisure.py b/homeassistant/components/switch/verisure.py index 52182bf94a9..cc331f12fdb 100644 --- a/homeassistant/components/switch/verisure.py +++ b/homeassistant/components/switch/verisure.py @@ -42,8 +42,8 @@ class VerisureSmartplug(SwitchDevice): """ Represents a Verisure smartplug. """ def __init__(self, smartplug_status): self._id = smartplug_status.id - self.STATUS_ON = verisure.MY_PAGES.SMARTPLUG_ON - self.STATUS_OFF = verisure.MY_PAGES.SMARTPLUG_OFF + self.status_off = verisure.MY_PAGES.SMARTPLUG_ON + self.status_off = verisure.MY_PAGES.SMARTPLUG_OFF @property def name(self): @@ -54,19 +54,19 @@ class VerisureSmartplug(SwitchDevice): def is_on(self): """ Returns True if on """ plug_status = verisure.get_smartplug_status()[self._id].status - return plug_status == self.STATUS_ON + return plug_status == self.status_off def turn_on(self): """ Set smartplug status on """ verisure.MY_PAGES.set_smartplug_status( self._id, - self.STATUS_ON) + self.status_on) def turn_off(self): """ Set smartplug status off. """ verisure.MY_PAGES.set_smartplug_status( self._id, - self.STATUS_OFF) + self.status_off) def update(self): verisure.update() diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index 6e0d6a81539..18a7701a3ab 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -64,7 +64,7 @@ def setup(hass, config): try: MY_PAGES.login() except (ConnectionError, Error) as ex: - _LOGGER.error('Could not log in to verisure mypages, %s', ex.message) + _LOGGER.error('Could not log in to verisure mypages, %s', ex) return False update() @@ -102,11 +102,11 @@ def reconnect(): try: MY_PAGES.login() except VERISURE_LOGIN_ERROR as ex: - _LOGGER.error("Could not login to Verisure mypages, %s", ex.message) + _LOGGER.error("Could not login to Verisure mypages, %s", ex) global WRONG_PASSWORD_GIVEN WRONG_PASSWORD_GIVEN = True except (ConnectionError, VERISURE_ERROR) as ex: - _LOGGER.error("Could not login to Verisure mypages, %s", ex.message) + _LOGGER.error("Could not login to Verisure mypages, %s", ex) @Throttle(MIN_TIME_BETWEEN_REQUESTS) From e37869616bcbdc78ef9dabef6780f17c93f7e793 Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Sun, 16 Aug 2015 08:03:19 +0200 Subject: [PATCH 25/33] no more duplicate sensors --- homeassistant/components/sensor/verisure.py | 4 +--- homeassistant/components/switch/verisure.py | 8 +++----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/sensor/verisure.py index 12ca073ceab..163d608d7d2 100644 --- a/homeassistant/components/sensor/verisure.py +++ b/homeassistant/components/sensor/verisure.py @@ -12,8 +12,6 @@ from homeassistant.const import TEMP_CELCIUS _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['verisure'] - def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up the Verisure platform. """ @@ -79,7 +77,7 @@ class VerisureAlarmDevice(Entity): @property def state(self): """ Returns the state of the device. """ - return verisure.STATUS[self._device][self._id].status + return verisure.STATUS[self._device][self._id].label def update(self): verisure.update() diff --git a/homeassistant/components/switch/verisure.py b/homeassistant/components/switch/verisure.py index cc331f12fdb..f71ca7550a7 100644 --- a/homeassistant/components/switch/verisure.py +++ b/homeassistant/components/switch/verisure.py @@ -16,8 +16,6 @@ import logging import homeassistant.components.verisure as verisure from homeassistant.components.switch import SwitchDevice -DEPENDENCIES = ['verisure'] - _LOGGER = logging.getLogger(__name__) @@ -42,7 +40,7 @@ class VerisureSmartplug(SwitchDevice): """ Represents a Verisure smartplug. """ def __init__(self, smartplug_status): self._id = smartplug_status.id - self.status_off = verisure.MY_PAGES.SMARTPLUG_ON + self.status_on = verisure.MY_PAGES.SMARTPLUG_ON self.status_off = verisure.MY_PAGES.SMARTPLUG_OFF @property @@ -54,7 +52,7 @@ class VerisureSmartplug(SwitchDevice): def is_on(self): """ Returns True if on """ plug_status = verisure.get_smartplug_status()[self._id].status - return plug_status == self.status_off + return plug_status == self.status_on def turn_on(self): """ Set smartplug status on """ @@ -63,7 +61,7 @@ class VerisureSmartplug(SwitchDevice): self.status_on) def turn_off(self): - """ Set smartplug status off. """ + """ Set smartplug status off """ verisure.MY_PAGES.set_smartplug_status( self._id, self.status_off) From 1b89a502c4f77e73a335e735adce0f36f518eeda Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 16 Aug 2015 20:44:46 -0700 Subject: [PATCH 26/33] Extract core into own submodule --- homeassistant/bootstrap.py | 13 +++++++------ homeassistant/components/__init__.py | 2 +- homeassistant/components/api.py | 2 +- homeassistant/components/conversation.py | 16 +++++++--------- homeassistant/components/demo.py | 2 +- homeassistant/components/group.py | 2 +- homeassistant/components/http.py | 2 +- homeassistant/components/logbook.py | 2 +- homeassistant/components/mqtt.py | 2 +- homeassistant/components/recorder.py | 2 +- homeassistant/components/scene.py | 2 +- homeassistant/config.py | 2 +- homeassistant/{__init__.py => core.py} | 0 homeassistant/helpers/entity.py | 2 +- homeassistant/helpers/state.py | 2 +- homeassistant/remote.py | 2 +- tests/common.py | 2 +- tests/components/automation/test_event.py | 2 +- tests/components/automation/test_init.py | 2 +- tests/components/automation/test_mqtt.py | 2 +- tests/components/automation/test_state.py | 2 +- tests/components/automation/test_time.py | 2 +- tests/components/test_api.py | 2 +- tests/components/test_configurator.py | 2 +- tests/components/test_demo.py | 2 +- tests/components/test_device_tracker.py | 2 +- tests/components/test_frontend.py | 2 +- tests/components/test_group.py | 2 +- tests/components/test_history.py | 2 +- tests/components/test_init.py | 2 +- tests/components/test_logbook.py | 2 +- tests/components/test_media_player.py | 2 +- tests/components/test_sun.py | 2 +- tests/helpers/test_entity.py | 2 +- tests/helpers/test_event.py | 2 +- tests/helpers/test_init.py | 2 +- tests/test_config.py | 2 +- tests/{test_init.py => test_core.py} | 0 tests/test_remote.py | 2 +- 39 files changed, 49 insertions(+), 50 deletions(-) rename homeassistant/{__init__.py => core.py} (100%) rename tests/{test_init.py => test_core.py} (100%) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 514c1adce57..e5f6d2b9672 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -13,7 +13,7 @@ import os import logging from collections import defaultdict -import homeassistant +import homeassistant.core as core import homeassistant.util.dt as date_util import homeassistant.util.package as pkg_util import homeassistant.util.location as loc_util @@ -152,9 +152,9 @@ def from_config_dict(config, hass=None): Dynamically loads required components and its dependencies. """ if hass is None: - hass = homeassistant.HomeAssistant() + hass = core.HomeAssistant() - process_ha_core_config(hass, config.get(homeassistant.DOMAIN, {})) + process_ha_core_config(hass, config.get(core.DOMAIN, {})) enable_logging(hass) @@ -168,7 +168,7 @@ def from_config_dict(config, hass=None): # Filter out the repeating and common config section [homeassistant] components = (key for key in config.keys() - if ' ' not in key and key != homeassistant.DOMAIN) + if ' ' not in key and key != core.DOMAIN) if not core_components.setup(hass, config): _LOGGER.error('Home Assistant core failed to initialize. ' @@ -192,7 +192,7 @@ def from_config_file(config_path, hass=None): instantiates a new Home Assistant object if 'hass' is not given. """ if hass is None: - hass = homeassistant.HomeAssistant() + hass = core.HomeAssistant() # Set config dir to directory holding config file hass.config.config_dir = os.path.abspath(os.path.dirname(config_path)) @@ -222,7 +222,8 @@ def enable_logging(hass): } )) except ImportError: - _LOGGER.warn("Colorlog package not found, console coloring disabled") + _LOGGER.warning( + "Colorlog package not found, console coloring disabled") # Log errors to a file if we have write access to file or config dir err_log_path = hass.config.path('home-assistant.log') diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index 0b757766bc0..e5e917c5250 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -17,7 +17,7 @@ Each component should publish services only under its own domain. import itertools as it import logging -import homeassistant as ha +import homeassistant.core as ha import homeassistant.util as util from homeassistant.helpers import extract_entity_ids from homeassistant.loader import get_component diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index b0d6faa2f49..108cc88741b 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -9,7 +9,7 @@ import logging import threading import json -import homeassistant as ha +import homeassistant.core as ha from homeassistant.helpers.state import TrackStates import homeassistant.remote as rem from homeassistant.const import ( diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py index bf78e13a094..2d439a7ac4a 100644 --- a/homeassistant/components/conversation.py +++ b/homeassistant/components/conversation.py @@ -8,7 +8,7 @@ This is more a proof of concept. import logging import re -import homeassistant +from homeassistant import core from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF) @@ -52,16 +52,14 @@ def setup(hass, config): return if command == 'on': - hass.services.call( - homeassistant.DOMAIN, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity_ids, - }, blocking=True) + hass.services.call(core.DOMAIN, SERVICE_TURN_ON, { + ATTR_ENTITY_ID: entity_ids, + }, blocking=True) elif command == 'off': - hass.services.call( - homeassistant.DOMAIN, SERVICE_TURN_OFF, { - ATTR_ENTITY_ID: entity_ids, - }, blocking=True) + hass.services.call(core.DOMAIN, SERVICE_TURN_OFF, { + ATTR_ENTITY_ID: entity_ids, + }, blocking=True) else: logger.error( diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index d0b8b155b4a..17d20571f62 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -6,7 +6,7 @@ Sets up a demo environment that mimics interaction with devices. """ import time -import homeassistant as ha +import homeassistant.core as ha import homeassistant.bootstrap as bootstrap import homeassistant.loader as loader from homeassistant.const import ( diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index 6975db4c46c..1d307baaca9 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -5,7 +5,7 @@ homeassistant.components.group Provides functionality to group devices that can be turned on or off. """ -import homeassistant as ha +import homeassistant.core as ha from homeassistant.helpers import generate_entity_id from homeassistant.helpers.event import track_state_change from homeassistant.helpers.entity import Entity diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index fcd17356885..a28def8e7ba 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -86,7 +86,7 @@ from http import cookies from socketserver import ThreadingMixIn from urllib.parse import urlparse, parse_qs -import homeassistant as ha +import homeassistant.core as ha from homeassistant.const import ( SERVER_PORT, CONTENT_TYPE_JSON, HTTP_HEADER_HA_AUTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_ACCEPT_ENCODING, diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py index acc9a36e494..c7a403f12ec 100644 --- a/homeassistant/components/logbook.py +++ b/homeassistant/components/logbook.py @@ -8,7 +8,7 @@ from datetime import timedelta from itertools import groupby import re -from homeassistant import State, DOMAIN as HA_DOMAIN +from homeassistant.core import State, DOMAIN as HA_DOMAIN from homeassistant.const import ( EVENT_STATE_CHANGED, STATE_HOME, STATE_ON, STATE_OFF, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, HTTP_BAD_REQUEST) diff --git a/homeassistant/components/mqtt.py b/homeassistant/components/mqtt.py index a892cc5015c..aa1a3167029 100644 --- a/homeassistant/components/mqtt.py +++ b/homeassistant/components/mqtt.py @@ -46,7 +46,7 @@ The keep alive in seconds for this client. Default is 60. import logging import socket -from homeassistant import HomeAssistantError +from homeassistant.core import HomeAssistantError import homeassistant.util as util from homeassistant.helpers import validate_config from homeassistant.const import ( diff --git a/homeassistant/components/recorder.py b/homeassistant/components/recorder.py index 5dca3bddde4..73487163425 100644 --- a/homeassistant/components/recorder.py +++ b/homeassistant/components/recorder.py @@ -13,7 +13,7 @@ from datetime import datetime, date import json import atexit -from homeassistant import Event, EventOrigin, State +from homeassistant.core import Event, EventOrigin, State import homeassistant.util.dt as date_util from homeassistant.remote import JSONEncoder from homeassistant.const import ( diff --git a/homeassistant/components/scene.py b/homeassistant/components/scene.py index a748e17ec5d..579ce1f20fb 100644 --- a/homeassistant/components/scene.py +++ b/homeassistant/components/scene.py @@ -18,7 +18,7 @@ old state will not be restored when it is being deactivated. import logging from collections import namedtuple -from homeassistant import State +from homeassistant.core import State from homeassistant.helpers.event import track_state_change from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent diff --git a/homeassistant/config.py b/homeassistant/config.py index 3b2dd1ce740..6ae40e9e7c7 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -7,7 +7,7 @@ Module to help with parsing and generating configuration files. import logging import os -from homeassistant import HomeAssistantError +from homeassistant.core import HomeAssistantError from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE) diff --git a/homeassistant/__init__.py b/homeassistant/core.py similarity index 100% rename from homeassistant/__init__.py rename to homeassistant/core.py diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index f9751ffc14c..0ca63856c27 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -7,7 +7,7 @@ Provides ABC for entities in HA. from collections import defaultdict -from homeassistant import NoEntitySpecifiedError +from homeassistant.core import NoEntitySpecifiedError from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN, diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 66e9a448d8e..d87ee48930c 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -6,7 +6,7 @@ Helpers that help with state related things. """ import logging -from homeassistant import State +from homeassistant.core import State import homeassistant.util.dt as dt_util from homeassistant.const import ( STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 5a0a828bb21..2488f0a9c46 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -17,7 +17,7 @@ import urllib.parse import requests -import homeassistant as ha +import homeassistant.core as ha import homeassistant.bootstrap as bootstrap from homeassistant.const import ( diff --git a/tests/common.py b/tests/common.py index d6dd6cd3279..be6aa623a25 100644 --- a/tests/common.py +++ b/tests/common.py @@ -8,7 +8,7 @@ import os from datetime import timedelta from unittest import mock -import homeassistant as ha +import homeassistant.core as ha import homeassistant.util.location as location_util import homeassistant.util.dt as dt_util from homeassistant.helpers.entity import ToggleEntity diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index dc685fa944d..a2c36283c9a 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -6,7 +6,7 @@ Tests demo component. """ import unittest -import homeassistant as ha +import homeassistant.core as ha import homeassistant.components.automation as automation import homeassistant.components.automation.event as event from homeassistant.const import CONF_PLATFORM diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 4bd0f4b96f3..2af17ea405c 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -6,7 +6,7 @@ Tests demo component. """ import unittest -import homeassistant as ha +import homeassistant.core as ha import homeassistant.components.automation as automation import homeassistant.components.automation.event as event from homeassistant.const import CONF_PLATFORM, ATTR_ENTITY_ID diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index 1fdc74bf3ce..9402b5300b6 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -6,7 +6,7 @@ Tests demo component. """ import unittest -import homeassistant as ha +import homeassistant.core as ha import homeassistant.components.automation as automation import homeassistant.components.automation.mqtt as mqtt from homeassistant.const import CONF_PLATFORM diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 5966946b278..47d612cbb02 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -6,7 +6,7 @@ Tests demo component. """ import unittest -import homeassistant as ha +import homeassistant.core as ha import homeassistant.components.automation as automation import homeassistant.components.automation.state as state from homeassistant.const import CONF_PLATFORM diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index 5581cc01348..0f11a2a67c5 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -6,7 +6,7 @@ Tests demo component. """ import unittest -import homeassistant as ha +import homeassistant.core as ha import homeassistant.loader as loader import homeassistant.util.dt as dt_util import homeassistant.components.automation as automation diff --git a/tests/components/test_api.py b/tests/components/test_api.py index ff25b476d32..93b1cd06abe 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -10,7 +10,7 @@ import json import requests -import homeassistant as ha +import homeassistant.core as ha import homeassistant.bootstrap as bootstrap import homeassistant.remote as remote import homeassistant.components.http as http diff --git a/tests/components/test_configurator.py b/tests/components/test_configurator.py index c64fc39e50a..f41a5319ffd 100644 --- a/tests/components/test_configurator.py +++ b/tests/components/test_configurator.py @@ -7,7 +7,7 @@ Tests Configurator component. # pylint: disable=too-many-public-methods,protected-access import unittest -import homeassistant as ha +import homeassistant.core as ha import homeassistant.components.configurator as configurator from homeassistant.const import EVENT_TIME_CHANGED diff --git a/tests/components/test_demo.py b/tests/components/test_demo.py index 9e697fb0c74..0abd546e4c4 100644 --- a/tests/components/test_demo.py +++ b/tests/components/test_demo.py @@ -6,7 +6,7 @@ Tests demo component. """ import unittest -import homeassistant as ha +import homeassistant.core as ha import homeassistant.components.demo as demo from tests.common import mock_http_component diff --git a/tests/components/test_device_tracker.py b/tests/components/test_device_tracker.py index bbc647987c9..66fd97c4730 100644 --- a/tests/components/test_device_tracker.py +++ b/tests/components/test_device_tracker.py @@ -10,7 +10,7 @@ from datetime import timedelta import logging import os -import homeassistant as ha +import homeassistant.core as ha import homeassistant.loader as loader import homeassistant.util.dt as dt_util from homeassistant.const import ( diff --git a/tests/components/test_frontend.py b/tests/components/test_frontend.py index d6431f5f5df..65fcb5b6091 100644 --- a/tests/components/test_frontend.py +++ b/tests/components/test_frontend.py @@ -10,7 +10,7 @@ import unittest import requests -import homeassistant as ha +import homeassistant.core as ha import homeassistant.bootstrap as bootstrap import homeassistant.components.http as http from homeassistant.const import HTTP_HEADER_HA_AUTH diff --git a/tests/components/test_group.py b/tests/components/test_group.py index 22256057d7a..d66a24606a3 100644 --- a/tests/components/test_group.py +++ b/tests/components/test_group.py @@ -8,7 +8,7 @@ Tests the group compoments. import unittest import logging -import homeassistant as ha +import homeassistant.core as ha from homeassistant.const import STATE_ON, STATE_OFF, STATE_HOME, STATE_UNKNOWN import homeassistant.components.group as group diff --git a/tests/components/test_history.py b/tests/components/test_history.py index 675e2d022d9..12d10c52744 100644 --- a/tests/components/test_history.py +++ b/tests/components/test_history.py @@ -9,7 +9,7 @@ import time import os import unittest -import homeassistant as ha +import homeassistant.core as ha import homeassistant.util.dt as dt_util from homeassistant.components import history, recorder diff --git a/tests/components/test_init.py b/tests/components/test_init.py index 8c00616bbb4..0074b75e148 100644 --- a/tests/components/test_init.py +++ b/tests/components/test_init.py @@ -7,7 +7,7 @@ Tests core compoments. # pylint: disable=protected-access,too-many-public-methods import unittest -import homeassistant as ha +import homeassistant.core as ha import homeassistant.loader as loader from homeassistant.const import ( STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF) diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index cd415590d2d..16f6ba8aa33 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -8,7 +8,7 @@ Tests the logbook component. import unittest from datetime import timedelta -import homeassistant as ha +import homeassistant.core as ha from homeassistant.const import ( EVENT_STATE_CHANGED, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) import homeassistant.util.dt as dt_util diff --git a/tests/components/test_media_player.py b/tests/components/test_media_player.py index f3f0128af3e..1fd406dc026 100644 --- a/tests/components/test_media_player.py +++ b/tests/components/test_media_player.py @@ -8,7 +8,7 @@ Tests media_player component. import logging import unittest -import homeassistant as ha +import homeassistant.core as ha from homeassistant.const import ( STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, diff --git a/tests/components/test_sun.py b/tests/components/test_sun.py index 705caadcd3a..9d2ae38fdd6 100644 --- a/tests/components/test_sun.py +++ b/tests/components/test_sun.py @@ -10,7 +10,7 @@ from datetime import timedelta from astral import Astral -import homeassistant as ha +import homeassistant.core as ha import homeassistant.util.dt as dt_util import homeassistant.components.sun as sun diff --git a/tests/helpers/test_entity.py b/tests/helpers/test_entity.py index 14559ded39a..b8823f23a5a 100644 --- a/tests/helpers/test_entity.py +++ b/tests/helpers/test_entity.py @@ -7,7 +7,7 @@ Tests the entity helper. # pylint: disable=protected-access,too-many-public-methods import unittest -import homeassistant as ha +import homeassistant.core as ha import homeassistant.helpers.entity as entity from homeassistant.const import ATTR_HIDDEN diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 69338cf431b..89711e2584e 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -9,7 +9,7 @@ Tests event helpers. import unittest from datetime import datetime -import homeassistant as ha +import homeassistant.core as ha from homeassistant.helpers.event import * diff --git a/tests/helpers/test_init.py b/tests/helpers/test_init.py index 9257dd634ef..c1af6ba8ccc 100644 --- a/tests/helpers/test_init.py +++ b/tests/helpers/test_init.py @@ -9,7 +9,7 @@ import unittest from common import get_test_home_assistant -import homeassistant as ha +import homeassistant.core as ha import homeassistant.loader as loader from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ENTITY_ID from homeassistant.helpers import extract_entity_ids diff --git a/tests/test_config.py b/tests/test_config.py index b8d050d14ae..f683fac890c 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -9,7 +9,7 @@ import unittest import unittest.mock as mock import os -from homeassistant import DOMAIN, HomeAssistantError +from homeassistant.core import DOMAIN, HomeAssistantError import homeassistant.config as config_util from homeassistant.const import ( CONF_LATITUDE, CONF_LONGITUDE, CONF_TEMPERATURE_UNIT, CONF_NAME, diff --git a/tests/test_init.py b/tests/test_core.py similarity index 100% rename from tests/test_init.py rename to tests/test_core.py diff --git a/tests/test_remote.py b/tests/test_remote.py index 7c00cbfd526..0f45091d598 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -9,7 +9,7 @@ Uses port 8125 as a port that nothing runs on # pylint: disable=protected-access,too-many-public-methods import unittest -import homeassistant as ha +import homeassistant.core as ha import homeassistant.bootstrap as bootstrap import homeassistant.remote as remote import homeassistant.components.http as http From 8bb189e014871bdab2a8e2dbe0e2042207332d37 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 16 Aug 2015 20:53:17 -0700 Subject: [PATCH 27/33] Finish core extraction --- homeassistant/__init__.py | 0 tests/test_core.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 homeassistant/__init__.py diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_core.py b/tests/test_core.py index 4d484be580e..6e7b52795b2 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -15,7 +15,7 @@ from datetime import datetime import pytz -import homeassistant as ha +import homeassistant.core as ha import homeassistant.util.dt as dt_util from homeassistant.helpers.event import track_state_change from homeassistant.const import ( From 9522eac8377f377cd8a9859366340eed64105383 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 16 Aug 2015 20:53:34 -0700 Subject: [PATCH 28/33] Fix mysensors import --- homeassistant/components/sensor/mysensors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index ad9649f9966..a626858db31 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """ Setup the mysensors platform. """ import mysensors.mysensors as mysensors - import mysensors.const as const + import mysensors.const_14 as const devices = {} # keep track of devices added to HA # Just assume celcius means that the user wants metric for now. From e984eedffd087f884df14258bee396aac08bc8d6 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 16 Aug 2015 20:53:34 -0700 Subject: [PATCH 29/33] Fix mysensors import --- homeassistant/components/sensor/mysensors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index ad9649f9966..a626858db31 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -44,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """ Setup the mysensors platform. """ import mysensors.mysensors as mysensors - import mysensors.const as const + import mysensors.const_14 as const devices = {} # keep track of devices added to HA # Just assume celcius means that the user wants metric for now. From b61b3c611d9f8fbae385bee610f72bf9340efa21 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 16 Aug 2015 21:36:33 -0700 Subject: [PATCH 30/33] Extract temperature util --- homeassistant/core.py | 6 ++++-- homeassistant/util/temperature.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 homeassistant/util/temperature.py diff --git a/homeassistant/core.py b/homeassistant/core.py index afc4ea8375f..76ff46cfe8b 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -23,6 +23,7 @@ from homeassistant.const import ( TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME) import homeassistant.util as util import homeassistant.util.dt as date_util +import homeassistant.util.temperature as temp_util DOMAIN = "homeassistant" @@ -674,10 +675,11 @@ class Config(object): try: if unit == TEMP_CELCIUS: # Convert C to F - return round(float(value) * 1.8 + 32.0, 1), TEMP_FAHRENHEIT + return (round(temp_util.c_to_f(float(value)), 1), + TEMP_FAHRENHEIT) # Convert F to C - return round((float(value)-32.0)/1.8, 1), TEMP_CELCIUS + return round(temp_util.f_to_c(float(value)), 1), TEMP_CELCIUS except ValueError: # Could not convert value to float diff --git a/homeassistant/util/temperature.py b/homeassistant/util/temperature.py new file mode 100644 index 00000000000..4cd0e1ed1d0 --- /dev/null +++ b/homeassistant/util/temperature.py @@ -0,0 +1,16 @@ +""" +homeassistant.util.temperature +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Temperature util functions. +""" + + +def f_to_c(fahrenheit): + """ Convert a Fahrenheit temperature to Celcius. """ + return (fahrenheit - 32.0) / 1.8 + + +def c_to_f(celcius): + """ Convert a Celcius temperature to Fahrenheit. """ + return celcius * 1.8 + 32.0 From 086961d1093b466583c9c8125515981b242c0a2a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 16 Aug 2015 22:06:01 -0700 Subject: [PATCH 31/33] Add temperature util, helpers --- homeassistant/core.py | 18 +++++++----------- homeassistant/helpers/temperature.py | 19 +++++++++++++++++++ homeassistant/util/temperature.py | 4 ++-- 3 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 homeassistant/helpers/temperature.py diff --git a/homeassistant/core.py b/homeassistant/core.py index 76ff46cfe8b..76b4b38f3fc 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -23,7 +23,7 @@ from homeassistant.const import ( TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME) import homeassistant.util as util import homeassistant.util.dt as date_util -import homeassistant.util.temperature as temp_util +import homeassistant.helpers.temperature as temp_helper DOMAIN = "homeassistant" @@ -673,18 +673,14 @@ class Config(object): return value, unit try: - if unit == TEMP_CELCIUS: - # Convert C to F - return (round(temp_util.c_to_f(float(value)), 1), - TEMP_FAHRENHEIT) - - # Convert F to C - return round(temp_util.f_to_c(float(value)), 1), TEMP_CELCIUS - - except ValueError: - # Could not convert value to float + temp = float(value) + except ValueError: # Could not convert value to float return value, unit + return ( + round(temp_helper.convert(temp, unit, self.temperature_unit), 1), + self.temperature_unit) + def as_dict(self): """ Converts config to a dictionary. """ time_zone = self.time_zone or date_util.UTC diff --git a/homeassistant/helpers/temperature.py b/homeassistant/helpers/temperature.py new file mode 100644 index 00000000000..eaf1f78d927 --- /dev/null +++ b/homeassistant/helpers/temperature.py @@ -0,0 +1,19 @@ +""" +homeassistant.helpers.temperature +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Methods to help handle temperature in Home Assistant. +""" + +from homeassistant.const import TEMP_CELCIUS +import homeassistant.util.temperature as temp_util + + +def convert(temperature, unit, to_unit): + """ Converts temperature to correct unit. """ + if unit == to_unit: + return temperature + elif unit == TEMP_CELCIUS: + return temp_util.celcius_to_fahrenheit(temperature) + + return temp_util.fahrenheit_to_celcius(temperature) diff --git a/homeassistant/util/temperature.py b/homeassistant/util/temperature.py index 4cd0e1ed1d0..658639aae55 100644 --- a/homeassistant/util/temperature.py +++ b/homeassistant/util/temperature.py @@ -6,11 +6,11 @@ Temperature util functions. """ -def f_to_c(fahrenheit): +def fahrenheit_to_celcius(fahrenheit): """ Convert a Fahrenheit temperature to Celcius. """ return (fahrenheit - 32.0) / 1.8 -def c_to_f(celcius): +def celcius_to_fahrenheit(celcius): """ Convert a Celcius temperature to Fahrenheit. """ return celcius * 1.8 + 32.0 From 4707b122cc3d4141501b75a2605bcb8cc8db975a Mon Sep 17 00:00:00 2001 From: Per Sandstrom Date: Mon, 17 Aug 2015 13:05:49 +0200 Subject: [PATCH 32/33] hygrometers and disabling of components --- homeassistant/components/sensor/verisure.py | 56 ++++++++++++++++++--- homeassistant/components/switch/verisure.py | 9 +--- homeassistant/components/verisure.py | 51 +++++++++++++++++++ 3 files changed, 102 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/sensor/verisure.py b/homeassistant/components/sensor/verisure.py index 163d608d7d2..61af1089775 100644 --- a/homeassistant/components/sensor/verisure.py +++ b/homeassistant/components/sensor/verisure.py @@ -23,20 +23,30 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensors = [] sensors.extend([ - VerisureClimateDevice(value) + VerisureThermometer(value) for value in verisure.get_climate_status().values() + if verisure.SHOW_THERMOMETERS and + hasattr(value, 'temperature') and value.temperature ]) sensors.extend([ - VerisureAlarmDevice(value) + VerisureHygrometer(value) + for value in verisure.get_climate_status().values() + if verisure.SHOW_HYGROMETERS and + hasattr(value, 'humidity') and value.humidity + ]) + + sensors.extend([ + VerisureAlarm(value) for value in verisure.get_alarm_status().values() + if verisure.SHOW_ALARM ]) add_devices(sensors) -class VerisureClimateDevice(Entity): - """ represents a Verisure climate sensor within home assistant. """ +class VerisureThermometer(Entity): + """ represents a Verisure thermometer within home assistant. """ def __init__(self, climate_status): self._id = climate_status.id @@ -45,7 +55,9 @@ class VerisureClimateDevice(Entity): @property def name(self): """ Returns the name of the device. """ - return verisure.STATUS[self._device][self._id].location + return '{} {}'.format( + verisure.STATUS[self._device][self._id].location, + "Temperature") @property def state(self): @@ -59,10 +71,41 @@ class VerisureClimateDevice(Entity): return TEMP_CELCIUS # can verisure report in fahrenheit? def update(self): + ''' update sensor ''' verisure.update() -class VerisureAlarmDevice(Entity): +class VerisureHygrometer(Entity): + """ represents a Verisure hygrometer within home assistant. """ + + def __init__(self, climate_status): + self._id = climate_status.id + self._device = verisure.MY_PAGES.DEVICE_CLIMATE + + @property + def name(self): + """ Returns the name of the device. """ + return '{} {}'.format( + verisure.STATUS[self._device][self._id].location, + "Humidity") + + @property + def state(self): + """ Returns the state of the device. """ + # remove % character + return verisure.STATUS[self._device][self._id].humidity[:-1] + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity """ + return "%" + + def update(self): + ''' update sensor ''' + verisure.update() + + +class VerisureAlarm(Entity): """ represents a Verisure alarm status within home assistant. """ def __init__(self, alarm_status): @@ -80,4 +123,5 @@ class VerisureAlarmDevice(Entity): return verisure.STATUS[self._device][self._id].label def update(self): + ''' update sensor ''' verisure.update() diff --git a/homeassistant/components/switch/verisure.py b/homeassistant/components/switch/verisure.py index f71ca7550a7..6c8f0352c3f 100644 --- a/homeassistant/components/switch/verisure.py +++ b/homeassistant/components/switch/verisure.py @@ -2,14 +2,6 @@ homeassistant.components.switch.verisure ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for Verisure Smartplugs - -Configuration: - -switch: - platform: verisure - -Variables: - """ import logging @@ -31,6 +23,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): switches.extend([ VerisureSmartplug(value) for value in verisure.get_smartplug_status().values() + if verisure.SHOW_SMARTPLUGS ]) add_devices(switches) diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index 18a7701a3ab..f084ce9874c 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -1,6 +1,45 @@ """ components.verisure ~~~~~~~~~~~~~~~~~~ + +Provides support for verisure components + +Configuration: + +verisure: + username: user@example.com + password: password + alarm: 1 + hygrometers: 0 + smartplugs: 1 + thermometers: 0 + + +Variables: + +username +*Required +Username to verisure mypages + +password +*Required +Password to verisure mypages + +alarm +*Opional +Set to 1 to show alarm, 0 to disable. Default 1 + +hygrometers +*Opional +Set to 1 to show hygrometers, 0 to disable. Default 1 + +smartplugs +*Opional +Set to 1 to show smartplugs, 0 to disable. Default 1 + +thermometers +*Opional +Set to 1 to show thermometers, 0 to disable. Default 1 """ import logging from datetime import timedelta @@ -33,6 +72,11 @@ STATUS = {} VERISURE_LOGIN_ERROR = None VERISURE_ERROR = None +SHOW_THERMOMETERS = True +SHOW_HYGROMETERS = True +SHOW_ALARM = True +SHOW_SMARTPLUGS = True + # if wrong password was given don't try again WRONG_PASSWORD_GIVEN = False @@ -53,6 +97,12 @@ def setup(hass, config): STATUS[MyPages.DEVICE_CLIMATE] = {} STATUS[MyPages.DEVICE_SMARTPLUG] = {} + global SHOW_THERMOMETERS, SHOW_HYGROMETERS, SHOW_ALARM, SHOW_SMARTPLUGS + SHOW_THERMOMETERS = int(config[DOMAIN].get('thermometers', '1')) + SHOW_HYGROMETERS = int(config[DOMAIN].get('hygrometers', '1')) + SHOW_ALARM = int(config[DOMAIN].get('alarm', '1')) + SHOW_SMARTPLUGS = int(config[DOMAIN].get('smartplugs', '1')) + global MY_PAGES MY_PAGES = MyPages( config[DOMAIN][CONF_USERNAME], @@ -73,6 +123,7 @@ def setup(hass, config): for comp_name, discovery in ((('sensor', DISCOVER_SENSORS), ('switch', DISCOVER_SWITCHES))): component = get_component(comp_name) + _LOGGER.info(config[DOMAIN]) bootstrap.setup_component(hass, component.DOMAIN, config) hass.bus.fire(EVENT_PLATFORM_DISCOVERED, From 4ab75d58f52c938070a2b478138fba85b0235591 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 17 Aug 2015 19:49:08 -0700 Subject: [PATCH 33/33] Update coveragerc --- .coveragerc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.coveragerc b/.coveragerc index 1dc94450eac..7c0421c384a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -16,15 +16,18 @@ omit = homeassistant/components/modbus.py homeassistant/components/*/modbus.py + homeassistant/components/*/tellstick.py + homeassistant/components/*/vera.py + + homeassistant/components/verisure.py + homeassistant/components/*/verisure.py + homeassistant/components/wink.py homeassistant/components/*/wink.py homeassistant/components/zwave.py homeassistant/components/*/zwave.py - homeassistant/components/*/tellstick.py - homeassistant/components/*/vera.py - homeassistant/components/browser.py homeassistant/components/camera/* homeassistant/components/device_tracker/asuswrt.py