From adfcfad48886298cfe949705969dc0e62699cf98 Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Wed, 23 Dec 2015 03:52:52 -0700 Subject: [PATCH 01/10] Update locative functionality --- .../components/device_tracker/locative.py | 112 +++++++++++++----- 1 file changed, 80 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index 2d238992cc7..0ed97b6c4f8 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -6,12 +6,15 @@ Locative platform for the device tracker. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/device_tracker.locative/ """ +import logging +from functools import partial + from homeassistant.const import ( - HTTP_UNPROCESSABLE_ENTITY, HTTP_INTERNAL_SERVER_ERROR) + HTTP_UNPROCESSABLE_ENTITY, HTTP_INTERNAL_SERVER_ERROR, STATE_NOT_HOME) -DEPENDENCIES = ['http'] +_LOGGER = logging.getLogger(__name__) -_SEE = 0 +DEPENDENCIES = ['http', 'zone'] URL_API_LOCATIVE_ENDPOINT = "/api/locative" @@ -19,52 +22,97 @@ URL_API_LOCATIVE_ENDPOINT = "/api/locative" def setup_scanner(hass, config, see): """ Set up an endpoint for the Locative app. """ - # Use a global variable to keep setup_scanner compact when using a callback - global _SEE - _SEE = see - # POST would be semantically better, but that currently does not work # since Locative sends the data as key1=value1&key2=value2 # in the request body, while Home Assistant expects json there. hass.http.register_path( - 'GET', URL_API_LOCATIVE_ENDPOINT, _handle_get_api_locative) + 'GET', URL_API_LOCATIVE_ENDPOINT, + partial(_handle_get_api_locative, hass, see)) return True -def _handle_get_api_locative(handler, path_match, data): +# TODO: What happens with HA turns off? +def _handle_get_api_locative(hass, see, handler, path_match, data): """ Locative message received. """ - if not isinstance(data, dict): - handler.write_json_message( - "Error while parsing Locative message.", - HTTP_INTERNAL_SERVER_ERROR) - return - if 'latitude' not in data or 'longitude' not in data: - handler.write_json_message( - "Location not specified.", - HTTP_UNPROCESSABLE_ENTITY) - return - if 'device' not in data or 'id' not in data: - handler.write_json_message( - "Device id or location id not specified.", - HTTP_UNPROCESSABLE_ENTITY) + if not _check_data(handler, data): return + device = data['device'].replace('-', '') + location_name = data['id'] + direction = data['trigger'] + try: gps_coords = (float(data['latitude']), float(data['longitude'])) except ValueError: - # If invalid latitude / longitude format - handler.write_json_message( - "Invalid latitude / longitude format.", - HTTP_UNPROCESSABLE_ENTITY) + handler.write_json_message("Invalid latitude / longitude format.", + HTTP_UNPROCESSABLE_ENTITY) + _LOGGER.error("Received invalid latitude / longitude format.") return - # entity id's in Home Assistant must be alphanumerical - device_uuid = data['device'] - device_entity_id = device_uuid.replace('-', '') + if direction == 'enter': + zones = [state for state in hass.states.entity_ids('zone')] + _LOGGER.info(zones) - _SEE(dev_id=device_entity_id, gps=gps_coords, location_name=data['id']) + if "zone.{}".format(location_name.lower()) in zones: + see(dev_id=device, location_name=location_name) + handler.write_json_message("Set new location to {}".format(location_name)) + else: + see(dev_id=device, gps=gps_coords) + handler.write_json_message("Set new location to {}".format(gps_coords)) + + elif direction == 'exit': + current_zone = hass.states.get("{}.{}".format("device_tracker", device)).state + + if current_zone.lower() == location_name.lower(): + see(dev_id=device, location_name=STATE_NOT_HOME) + handler.write_json_message("Set new location to not home") + else: + # Ignore the message if it is telling us to exit a zone that we aren't + # currently in. This occurs when a zone is entered before the previous + # zone was exited. The enter message will be sent first, then the exit + # message will be sent second. + handler.write_json_message("Ignoring transition to {}".format(location_name)) + + else: + handler.write_json_message("Received unidentified message: {}".format(direction)) + _LOGGER.error("Received unidentified message from Locative: %s", + direction) + + +def _check_data(handler, data): + if not isinstance(data, dict): + handler.write_json_message("Error while parsing Locative message.", + HTTP_INTERNAL_SERVER_ERROR) + _LOGGER.error("Error while parsing Locative message: " + "data is not a dict.") + return False + + if 'latitude' not in data or 'longitude' not in data: + handler.write_json_message("Latitude and longitude not specified.", + HTTP_UNPROCESSABLE_ENTITY) + _LOGGER.error("Latitude and longitude not specified.") + return False + + if 'device' not in data: + handler.write_json_message("Device id not specified.", + HTTP_UNPROCESSABLE_ENTITY) + _LOGGER.error("Device id not specified.") + return False + + if 'id' not in data: + handler.write_json_message("Location id not specified.", + HTTP_UNPROCESSABLE_ENTITY) + _LOGGER.error("Location id not specified.") + return False + + if 'trigger' not in data: + handler.write_json_message("Trigger is not specified.", + HTTP_UNPROCESSABLE_ENTITY) + _LOGGER.error("Trigger is not specified.") + return False + + return True - handler.write_json_message("Locative message processed") From 25e1432403f2afc9ca050fc530122f63a38c0fa7 Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Wed, 30 Dec 2015 12:30:49 -0700 Subject: [PATCH 02/10] Fix style issues --- .../components/device_tracker/locative.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index 0ed97b6c4f8..cb8e42fd1c4 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -33,7 +33,6 @@ def setup_scanner(hass, config, see): return True -# TODO: What happens with HA turns off? def _handle_get_api_locative(hass, see, handler, path_match, data): """ Locative message received. """ @@ -58,26 +57,31 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): if "zone.{}".format(location_name.lower()) in zones: see(dev_id=device, location_name=location_name) - handler.write_json_message("Set new location to {}".format(location_name)) + handler.write_json_message( + "Set new location to {}".format(location_name)) else: see(dev_id=device, gps=gps_coords) - handler.write_json_message("Set new location to {}".format(gps_coords)) + handler.write_json_message( + "Set new location to {}".format(gps_coords)) elif direction == 'exit': - current_zone = hass.states.get("{}.{}".format("device_tracker", device)).state + current_zone = hass.states.get( + "{}.{}".format("device_tracker", device)).state if current_zone.lower() == location_name.lower(): see(dev_id=device, location_name=STATE_NOT_HOME) handler.write_json_message("Set new location to not home") else: - # Ignore the message if it is telling us to exit a zone that we aren't - # currently in. This occurs when a zone is entered before the previous - # zone was exited. The enter message will be sent first, then the exit - # message will be sent second. - handler.write_json_message("Ignoring transition to {}".format(location_name)) + # Ignore the message if it is telling us to exit a zone that we + # aren't currently in. This occurs when a zone is entered before + # the previous zone was exited. The enter message will be sent + # first, then the exit message will be sent second. + handler.write_json_message( + "Ignoring transition to {}".format(location_name)) else: - handler.write_json_message("Received unidentified message: {}".format(direction)) + handler.write_json_message( + "Received unidentified message: {}".format(direction)) _LOGGER.error("Received unidentified message from Locative: %s", direction) @@ -115,4 +119,3 @@ def _check_data(handler, data): return False return True - From c23375a18b1793dfe0e9066763c85bf45cbf255c Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Wed, 30 Dec 2015 17:30:20 -0700 Subject: [PATCH 03/10] Add case for test message --- homeassistant/components/device_tracker/locative.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index cb8e42fd1c4..72e458bc314 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -77,7 +77,12 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): # the previous zone was exited. The enter message will be sent # first, then the exit message will be sent second. handler.write_json_message( - "Ignoring transition to {}".format(location_name)) + "Ignoring transition from {}".format(location_name)) + + elif direction == 'test': + # In the app, a test message can be sent. Just return something to + # the user to let them know that it works. + handler.write_text("Received test message.") else: handler.write_json_message( From 7d41ce4e46c0f6a77218621619bd287c550a4665 Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Wed, 30 Dec 2015 22:43:00 -0700 Subject: [PATCH 04/10] Switch from json messages to plain text messages --- .../components/device_tracker/locative.py | 34 +++++++++---------- homeassistant/components/http.py | 14 +++++++- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index 72e458bc314..c635aa47858 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -46,8 +46,8 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): try: gps_coords = (float(data['latitude']), float(data['longitude'])) except ValueError: - handler.write_json_message("Invalid latitude / longitude format.", - HTTP_UNPROCESSABLE_ENTITY) + handler.write_text("Invalid latitude / longitude format.", + HTTP_UNPROCESSABLE_ENTITY) _LOGGER.error("Received invalid latitude / longitude format.") return @@ -57,11 +57,11 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): if "zone.{}".format(location_name.lower()) in zones: see(dev_id=device, location_name=location_name) - handler.write_json_message( + handler.write_text( "Set new location to {}".format(location_name)) else: see(dev_id=device, gps=gps_coords) - handler.write_json_message( + handler.write_text( "Set new location to {}".format(gps_coords)) elif direction == 'exit': @@ -70,13 +70,13 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): if current_zone.lower() == location_name.lower(): see(dev_id=device, location_name=STATE_NOT_HOME) - handler.write_json_message("Set new location to not home") + handler.write_text("Set new location to not home") else: # Ignore the message if it is telling us to exit a zone that we # aren't currently in. This occurs when a zone is entered before # the previous zone was exited. The enter message will be sent # first, then the exit message will be sent second. - handler.write_json_message( + handler.write_text( "Ignoring transition from {}".format(location_name)) elif direction == 'test': @@ -85,7 +85,7 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): handler.write_text("Received test message.") else: - handler.write_json_message( + handler.write_text( "Received unidentified message: {}".format(direction)) _LOGGER.error("Received unidentified message from Locative: %s", direction) @@ -93,33 +93,33 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): def _check_data(handler, data): if not isinstance(data, dict): - handler.write_json_message("Error while parsing Locative message.", - HTTP_INTERNAL_SERVER_ERROR) + handler.write_text("Error while parsing Locative message.", + HTTP_INTERNAL_SERVER_ERROR) _LOGGER.error("Error while parsing Locative message: " "data is not a dict.") return False if 'latitude' not in data or 'longitude' not in data: - handler.write_json_message("Latitude and longitude not specified.", - HTTP_UNPROCESSABLE_ENTITY) + handler.write_text("Latitude and longitude not specified.", + HTTP_UNPROCESSABLE_ENTITY) _LOGGER.error("Latitude and longitude not specified.") return False if 'device' not in data: - handler.write_json_message("Device id not specified.", - HTTP_UNPROCESSABLE_ENTITY) + handler.write_text("Device id not specified.", + HTTP_UNPROCESSABLE_ENTITY) _LOGGER.error("Device id not specified.") return False if 'id' not in data: - handler.write_json_message("Location id not specified.", - HTTP_UNPROCESSABLE_ENTITY) + handler.write_text("Location id not specified.", + HTTP_UNPROCESSABLE_ENTITY) _LOGGER.error("Location id not specified.") return False if 'trigger' not in data: - handler.write_json_message("Trigger is not specified.", - HTTP_UNPROCESSABLE_ENTITY) + handler.write_text("Trigger is not specified.", + HTTP_UNPROCESSABLE_ENTITY) _LOGGER.error("Trigger is not specified.") return False diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 7a4e87de5a8..cd701c24bb6 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -21,7 +21,7 @@ from urllib.parse import urlparse, parse_qs import homeassistant.core as ha from homeassistant.const import ( - SERVER_PORT, CONTENT_TYPE_JSON, + SERVER_PORT, CONTENT_TYPE_JSON, CONTENT_TYPE_TEXT_PLAIN, HTTP_HEADER_HA_AUTH, HTTP_HEADER_CONTENT_TYPE, HTTP_HEADER_ACCEPT_ENCODING, HTTP_HEADER_CONTENT_ENCODING, HTTP_HEADER_VARY, HTTP_HEADER_CONTENT_LENGTH, HTTP_HEADER_CACHE_CONTROL, HTTP_HEADER_EXPIRES, HTTP_OK, HTTP_UNAUTHORIZED, @@ -293,6 +293,18 @@ class RequestHandler(SimpleHTTPRequestHandler): json.dumps(data, indent=4, sort_keys=True, cls=rem.JSONEncoder).encode("UTF-8")) + def write_text(self, message, status_code=HTTP_OK): + """ Helper method to return a text message to the caller. """ + self.send_response(status_code) + self.send_header(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN) + + self.set_session_cookie_header() + + self.end_headers() + + if message is not None: + self.wfile.write(message.encode("UTF-8")) + def write_file(self, path, cache_headers=True): """ Returns a file to the user. """ try: From 55d1ad94ef8a34fb0c5e5b06e709e79a3be4d322 Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Thu, 31 Dec 2015 00:33:48 -0700 Subject: [PATCH 05/10] Add tests for Locative --- .coveragerc | 1 - .../device_tracker/test_locative.py | 246 ++++++++++++++++++ 2 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 tests/components/device_tracker/test_locative.py diff --git a/.coveragerc b/.coveragerc index 4b916a7fbcd..f5d1789a174 100644 --- a/.coveragerc +++ b/.coveragerc @@ -41,7 +41,6 @@ omit = homeassistant/components/device_tracker/asuswrt.py homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/fritz.py - homeassistant/components/device_tracker/locative.py homeassistant/components/device_tracker/icloud.py homeassistant/components/device_tracker/luci.py homeassistant/components/device_tracker/netgear.py diff --git a/tests/components/device_tracker/test_locative.py b/tests/components/device_tracker/test_locative.py new file mode 100644 index 00000000000..a6e5a431d36 --- /dev/null +++ b/tests/components/device_tracker/test_locative.py @@ -0,0 +1,246 @@ +""" +tests.components.device_tracker.locative +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests the locative device tracker component. +""" + +import unittest +from unittest.mock import patch + +import requests + +from homeassistant import bootstrap, const +import homeassistant.core as ha +import homeassistant.components.device_tracker as device_tracker +import homeassistant.components.http as http +import homeassistant.components.zone as zone + +SERVER_PORT = 8126 +HTTP_BASE_URL = "http://127.0.0.1:{}".format(SERVER_PORT) + +hass = None + + +def _url(data={}): + """ Helper method to generate urls. """ + data = "&".join(["{}={}".format(name, value) for name, value in data.items()]) + return "{}{}locative?{}".format(HTTP_BASE_URL, const.URL_API, data) + + +@patch('homeassistant.components.http.util.get_local_ip', + return_value='127.0.0.1') +def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name + """ Initalizes a Home Assistant server. """ + global hass + + hass = ha.HomeAssistant() + + # Set up a couple of zones + bootstrap.setup_component(hass, zone.DOMAIN, { + zone.DOMAIN: [ + { + 'name': 'Home', + 'latitude': 41.7855, + 'longitude': -110.7367, + 'radius': 200 + }, + { + 'name': 'Work', + 'latitude': 41.5855, + 'longitude': -110.9367, + 'radius': 100 + } + ] + }) + + # Set up server + bootstrap.setup_component(hass, http.DOMAIN, { + http.DOMAIN: { + http.CONF_SERVER_PORT: SERVER_PORT + } + }) + + # Set up API + bootstrap.setup_component(hass, 'api') + + # Set up device tracker + bootstrap.setup_component(hass, device_tracker.DOMAIN, { + device_tracker.DOMAIN: { + 'platform': 'locative' + } + }) + + hass.start() + + +def tearDownModule(): # pylint: disable=invalid-name + """ Stops the Home Assistant server. """ + hass.stop() + + +class TestLocative(unittest.TestCase): + """ Test Locative """ + + def test_missing_data(self): + data = { + 'latitude': 1.0, + 'longitude': 1.1, + 'device': '123', + 'id': 'Home', + 'trigger': 'enter' + } + + # No data + req = requests.get(_url({})) + self.assertEqual(422, req.status_code) + + # No latitude + copy = data.copy() + del copy['latitude'] + req = requests.get(_url(copy)) + self.assertEqual(422, req.status_code) + + # No device + copy = data.copy() + del copy['device'] + req = requests.get(_url(copy)) + self.assertEqual(422, req.status_code) + + # No location + copy = data.copy() + del copy['id'] + req = requests.get(_url(copy)) + self.assertEqual(422, req.status_code) + + # No trigger + copy = data.copy() + del copy['trigger'] + req = requests.get(_url(copy)) + self.assertEqual(422, req.status_code) + + # Bad longitude + copy = data.copy() + copy['longitude'] = 'hello world' + req = requests.get(_url(copy)) + self.assertEqual(422, req.status_code) + + # Test message + copy = data.copy() + copy['trigger'] = 'test' + req = requests.get(_url(copy)) + self.assertEqual(200, req.status_code) + + # Unknown trigger + copy = data.copy() + copy['trigger'] = 'foobar' + req = requests.get(_url(copy)) + self.assertEqual(422, req.status_code) + + + def test_known_zone(self): + """ Test when there is a known zone """ + data = { + 'latitude': 40.7855, + 'longitude': -111.7367, + 'device': '123', + 'id': 'Home', + 'trigger': 'enter' + } + + # Enter the Home + req = requests.get(_url(data)) + self.assertEqual(200, req.status_code) + state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state + self.assertEqual(state_name, 'home') + + data['id'] = 'HOME' + data['trigger'] = 'exit' + + # Exit Home + req = requests.get(_url(data)) + self.assertEqual(200, req.status_code) + state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state + self.assertEqual(state_name, 'not_home') + + data['id'] = 'hOmE' + data['trigger'] = 'enter' + + # Enter Home again + req = requests.get(_url(data)) + self.assertEqual(200, req.status_code) + state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state + self.assertEqual(state_name, 'home') + + + def test_unknown_zone(self): + """ Test when there is no known zone """ + data = { + 'latitude': 40.7855, + 'longitude': -111.7367, + 'device': '123', + 'id': 'Foobar', + 'trigger': 'enter' + } + + # Enter Foobar + req = requests.get(_url(data)) + self.assertEqual(200, req.status_code) + + state = hass.states.get('{}.{}'.format('device_tracker', data['device'])) + self.assertEqual(state.state, 'not_home') + self.assertEqual(state.attributes['latitude'], data['latitude']) + self.assertEqual(state.attributes['longitude'], data['longitude']) + + data['trigger'] = 'exit' + + # Exit Foobar + req = requests.get(_url(data)) + self.assertEqual(200, req.status_code) + + state = hass.states.get('{}.{}'.format('device_tracker', data['device'])) + self.assertEqual(state.state, 'not_home') + self.assertEqual(state.attributes['latitude'], data['latitude']) + self.assertEqual(state.attributes['longitude'], data['longitude']) + + + def test_exit_after_enter(self): + """ Test when an exit message comes after an enter message """ + + data = { + 'latitude': 40.7855, + 'longitude': -111.7367, + 'device': '123', + 'id': 'Home', + 'trigger': 'enter' + } + + # Enter Home + req = requests.get(_url(data)) + self.assertEqual(200, req.status_code) + + state = hass.states.get('{}.{}'.format('device_tracker', data['device'])) + self.assertEqual(state.state, 'home') + + data['id'] = 'Work' + + # Enter Work + req = requests.get(_url(data)) + self.assertEqual(200, req.status_code) + + state = hass.states.get('{}.{}'.format('device_tracker', data['device'])) + self.assertEqual(state.state, 'work') + + data['id'] = 'Home' + data['trigger'] = 'exit' + + # Exit Home + req = requests.get(_url(data)) + self.assertEqual(200, req.status_code) + + state = hass.states.get('{}.{}'.format('device_tracker', data['device'])) + self.assertEqual(state.state, 'work') + + print(req.text) + + From bdb6182921b0319aa347199393bd9f4494ab2c85 Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Thu, 31 Dec 2015 00:34:06 -0700 Subject: [PATCH 06/10] Changes to locative based on tests --- .../components/device_tracker/locative.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index c635aa47858..3263c424c74 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -40,7 +40,7 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): return device = data['device'].replace('-', '') - location_name = data['id'] + location_name = data['id'].lower() direction = data['trigger'] try: @@ -53,9 +53,8 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): if direction == 'enter': zones = [state for state in hass.states.entity_ids('zone')] - _LOGGER.info(zones) - if "zone.{}".format(location_name.lower()) in zones: + if "zone.{}".format(location_name) in zones: see(dev_id=device, location_name=location_name) handler.write_text( "Set new location to {}".format(location_name)) @@ -68,7 +67,7 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): current_zone = hass.states.get( "{}.{}".format("device_tracker", device)).state - if current_zone.lower() == location_name.lower(): + if current_zone == location_name: see(dev_id=device, location_name=STATE_NOT_HOME) handler.write_text("Set new location to not home") else: @@ -77,7 +76,9 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): # the previous zone was exited. The enter message will be sent # first, then the exit message will be sent second. handler.write_text( - "Ignoring transition from {}".format(location_name)) + 'Ignoring exit from "{}". Already in "{}".'.format( + location_name, + current_zone.split('.')[-1])) elif direction == 'test': # In the app, a test message can be sent. Just return something to @@ -86,7 +87,8 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): else: handler.write_text( - "Received unidentified message: {}".format(direction)) + "Received unidentified message: {}".format(direction), + HTTP_UNPROCESSABLE_ENTITY) _LOGGER.error("Received unidentified message from Locative: %s", direction) From 1bcca8cba14f3dcac9efd41141b5141517c3be00 Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Thu, 31 Dec 2015 00:52:12 -0700 Subject: [PATCH 07/10] Fix problem with test --- tests/components/device_tracker/test_locative.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/components/device_tracker/test_locative.py b/tests/components/device_tracker/test_locative.py index a6e5a431d36..81c8152238e 100644 --- a/tests/components/device_tracker/test_locative.py +++ b/tests/components/device_tracker/test_locative.py @@ -78,11 +78,12 @@ def tearDownModule(): # pylint: disable=invalid-name """ Stops the Home Assistant server. """ hass.stop() - +# Stub out update_config or else Travis CI raises an exception +@patch('homeassistant.components.device_tracker.update_config') class TestLocative(unittest.TestCase): """ Test Locative """ - def test_missing_data(self): + def test_missing_data(self, update_config): data = { 'latitude': 1.0, 'longitude': 1.1, @@ -138,7 +139,7 @@ class TestLocative(unittest.TestCase): self.assertEqual(422, req.status_code) - def test_known_zone(self): + def test_known_zone(self, update_config): """ Test when there is a known zone """ data = { 'latitude': 40.7855, @@ -173,7 +174,7 @@ class TestLocative(unittest.TestCase): self.assertEqual(state_name, 'home') - def test_unknown_zone(self): + def test_unknown_zone(self, update_config): """ Test when there is no known zone """ data = { 'latitude': 40.7855, @@ -204,7 +205,7 @@ class TestLocative(unittest.TestCase): self.assertEqual(state.attributes['longitude'], data['longitude']) - def test_exit_after_enter(self): + def test_exit_after_enter(self, update_config): """ Test when an exit message comes after an enter message """ data = { @@ -240,7 +241,3 @@ class TestLocative(unittest.TestCase): state = hass.states.get('{}.{}'.format('device_tracker', data['device'])) self.assertEqual(state.state, 'work') - - print(req.text) - - From 5d953061e823c585a3b27ee182f45287304d27a0 Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Thu, 31 Dec 2015 01:43:18 -0700 Subject: [PATCH 08/10] Remove unnecessary error checking --- .../components/device_tracker/locative.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index 3263c424c74..f4fd72d0c5f 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -10,7 +10,7 @@ import logging from functools import partial from homeassistant.const import ( - HTTP_UNPROCESSABLE_ENTITY, HTTP_INTERNAL_SERVER_ERROR, STATE_NOT_HOME) + HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME) _LOGGER = logging.getLogger(__name__) @@ -57,11 +57,11 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): if "zone.{}".format(location_name) in zones: see(dev_id=device, location_name=location_name) handler.write_text( - "Set new location to {}".format(location_name)) + "Setting location to {}".format(location_name)) else: see(dev_id=device, gps=gps_coords) handler.write_text( - "Set new location to {}".format(gps_coords)) + "Setting location to {}".format(gps_coords)) elif direction == 'exit': current_zone = hass.states.get( @@ -69,14 +69,14 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): if current_zone == location_name: see(dev_id=device, location_name=STATE_NOT_HOME) - handler.write_text("Set new location to not home") + handler.write_text("Setting location to not home") else: # Ignore the message if it is telling us to exit a zone that we # aren't currently in. This occurs when a zone is entered before # the previous zone was exited. The enter message will be sent # first, then the exit message will be sent second. handler.write_text( - 'Ignoring exit from "{}". Already in "{}".'.format( + 'Ignoring exit from {} (already in {})'.format( location_name, current_zone.split('.')[-1])) @@ -94,13 +94,6 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): def _check_data(handler, data): - if not isinstance(data, dict): - handler.write_text("Error while parsing Locative message.", - HTTP_INTERNAL_SERVER_ERROR) - _LOGGER.error("Error while parsing Locative message: " - "data is not a dict.") - return False - if 'latitude' not in data or 'longitude' not in data: handler.write_text("Latitude and longitude not specified.", HTTP_UNPROCESSABLE_ENTITY) From ce152e9c94a5212f60f1c5560f8b7f36d9586eb5 Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Thu, 31 Dec 2015 12:02:50 -0700 Subject: [PATCH 09/10] Simplify logic --- .../components/device_tracker/locative.py | 32 +++------- .../device_tracker/test_locative.py | 62 ++++--------------- 2 files changed, 20 insertions(+), 74 deletions(-) diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index f4fd72d0c5f..e7532d1075d 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -11,10 +11,11 @@ from functools import partial from homeassistant.const import ( HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME) +from homeassistant.components.device_tracker import DOMAIN _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['http', 'zone'] +DEPENDENCIES = ['http'] URL_API_LOCATIVE_ENDPOINT = "/api/locative" @@ -43,31 +44,15 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): location_name = data['id'].lower() direction = data['trigger'] - try: - gps_coords = (float(data['latitude']), float(data['longitude'])) - except ValueError: - handler.write_text("Invalid latitude / longitude format.", - HTTP_UNPROCESSABLE_ENTITY) - _LOGGER.error("Received invalid latitude / longitude format.") - return - if direction == 'enter': - zones = [state for state in hass.states.entity_ids('zone')] - - if "zone.{}".format(location_name) in zones: - see(dev_id=device, location_name=location_name) - handler.write_text( - "Setting location to {}".format(location_name)) - else: - see(dev_id=device, gps=gps_coords) - handler.write_text( - "Setting location to {}".format(gps_coords)) + see(dev_id=device, location_name=location_name) + handler.write_text("Setting location to {}".format(location_name)) elif direction == 'exit': - current_zone = hass.states.get( - "{}.{}".format("device_tracker", device)).state + current_state = hass.states.get( + "{}.{}".format(DOMAIN, device)).state - if current_zone == location_name: + if current_state == location_name: see(dev_id=device, location_name=STATE_NOT_HOME) handler.write_text("Setting location to not home") else: @@ -77,8 +62,7 @@ def _handle_get_api_locative(hass, see, handler, path_match, data): # first, then the exit message will be sent second. handler.write_text( 'Ignoring exit from {} (already in {})'.format( - location_name, - current_zone.split('.')[-1])) + location_name, current_state)) elif direction == 'test': # In the app, a test message can be sent. Just return something to diff --git a/tests/components/device_tracker/test_locative.py b/tests/components/device_tracker/test_locative.py index 81c8152238e..b86f24455de 100644 --- a/tests/components/device_tracker/test_locative.py +++ b/tests/components/device_tracker/test_locative.py @@ -36,24 +36,6 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name hass = ha.HomeAssistant() - # Set up a couple of zones - bootstrap.setup_component(hass, zone.DOMAIN, { - zone.DOMAIN: [ - { - 'name': 'Home', - 'latitude': 41.7855, - 'longitude': -110.7367, - 'radius': 200 - }, - { - 'name': 'Work', - 'latitude': 41.5855, - 'longitude': -110.9367, - 'radius': 100 - } - ] - }) - # Set up server bootstrap.setup_component(hass, http.DOMAIN, { http.DOMAIN: { @@ -120,12 +102,6 @@ class TestLocative(unittest.TestCase): req = requests.get(_url(copy)) self.assertEqual(422, req.status_code) - # Bad longitude - copy = data.copy() - copy['longitude'] = 'hello world' - req = requests.get(_url(copy)) - self.assertEqual(422, req.status_code) - # Test message copy = data.copy() copy['trigger'] = 'test' @@ -139,7 +115,7 @@ class TestLocative(unittest.TestCase): self.assertEqual(422, req.status_code) - def test_known_zone(self, update_config): + def test_enter_and_exit(self, update_config): """ Test when there is a known zone """ data = { 'latitude': 40.7855, @@ -173,36 +149,22 @@ class TestLocative(unittest.TestCase): state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state self.assertEqual(state_name, 'home') - - def test_unknown_zone(self, update_config): - """ Test when there is no known zone """ - data = { - 'latitude': 40.7855, - 'longitude': -111.7367, - 'device': '123', - 'id': 'Foobar', - 'trigger': 'enter' - } - - # Enter Foobar - req = requests.get(_url(data)) - self.assertEqual(200, req.status_code) - - state = hass.states.get('{}.{}'.format('device_tracker', data['device'])) - self.assertEqual(state.state, 'not_home') - self.assertEqual(state.attributes['latitude'], data['latitude']) - self.assertEqual(state.attributes['longitude'], data['longitude']) - data['trigger'] = 'exit' - # Exit Foobar + # Exit Home req = requests.get(_url(data)) self.assertEqual(200, req.status_code) + state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state + self.assertEqual(state_name, 'not_home') - state = hass.states.get('{}.{}'.format('device_tracker', data['device'])) - self.assertEqual(state.state, 'not_home') - self.assertEqual(state.attributes['latitude'], data['latitude']) - self.assertEqual(state.attributes['longitude'], data['longitude']) + data['id'] = 'work' + data['trigger'] = 'enter' + + # Enter Work + req = requests.get(_url(data)) + self.assertEqual(200, req.status_code) + state_name = hass.states.get('{}.{}'.format('device_tracker', data['device'])).state + self.assertEqual(state_name, 'work') def test_exit_after_enter(self, update_config): From 394c87c40b23eeb50d293a31bb9655d0c32dda43 Mon Sep 17 00:00:00 2001 From: Philip Lundrigan Date: Thu, 31 Dec 2015 13:05:24 -0700 Subject: [PATCH 10/10] Remove unnecessary condition in write_text --- homeassistant/components/http.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index cd701c24bb6..b7f57b0157e 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -302,8 +302,7 @@ class RequestHandler(SimpleHTTPRequestHandler): self.end_headers() - if message is not None: - self.wfile.write(message.encode("UTF-8")) + self.wfile.write(message.encode("UTF-8")) def write_file(self, path, cache_headers=True): """ Returns a file to the user. """