From 75e6ed87d6f60ab5419cdfe5ac4ec71295be3b02 Mon Sep 17 00:00:00 2001 From: NMA Date: Fri, 12 Aug 2016 14:48:28 +0530 Subject: [PATCH 01/13] Backend support for importing waypoints from owntracks as HA zones --- .../components/device_tracker/owntracks.py | 69 +++++++++++++------ homeassistant/components/zone.py | 23 +++++-- 2 files changed, 66 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index 00ba8c68556..13cc918a436 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -12,6 +12,7 @@ from collections import defaultdict import homeassistant.components.mqtt as mqtt from homeassistant.const import STATE_HOME from homeassistant.util import convert, slugify +from homeassistant.components import zone DEPENDENCIES = ['mqtt'] @@ -22,17 +23,19 @@ BEACON_DEV_ID = 'beacon' LOCATION_TOPIC = 'owntracks/+/+' EVENT_TOPIC = 'owntracks/+/+/event' +WAYPOINT_TOPIC = 'owntracks/{}/+/waypoint' _LOGGER = logging.getLogger(__name__) LOCK = threading.Lock() CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy' - +CONF_WAYPOINT_IMPORT_USER = 'waypoint_import_user' def setup_scanner(hass, config, see): """Setup an OwnTracks tracker.""" max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY) + waypoint_import_user = config.get(CONF_WAYPOINT_IMPORT_USER) def validate_payload(payload, data_type): """Validate OwnTracks payload.""" @@ -47,17 +50,18 @@ def setup_scanner(hass, config, see): 'because of missing or malformatted data: %s', data_type, data) return None - if max_gps_accuracy is not None and \ - convert(data.get('acc'), float, 0.0) > max_gps_accuracy: - _LOGGER.debug('Skipping %s update because expected GPS ' - 'accuracy %s is not met: %s', - data_type, max_gps_accuracy, data) - return None - if convert(data.get('acc'), float, 1.0) == 0.0: - _LOGGER.debug('Skipping %s update because GPS accuracy' - 'is zero', - data_type) - return None + if data_type != 'waypoints': + if max_gps_accuracy is not None and \ + convert(data.get('acc'), float, 0.0) > max_gps_accuracy: + _LOGGER.debug('Skipping %s update because expected GPS ' + 'accuracy %s is not met: %s', + data_type, max_gps_accuracy, data) + return None + if convert(data.get('acc'), float, 1.0) == 0.0: + _LOGGER.debug('Skipping %s update because GPS accuracy' + 'is zero', + data_type) + return None return data @@ -105,9 +109,9 @@ def setup_scanner(hass, config, see): def enter_event(): """Execute enter event.""" - zone = hass.states.get("zone.{}".format(location)) + _zone = hass.states.get("zone.{}".format(location)) with LOCK: - if zone is None and data.get('t') == 'b': + if _zone is None and data.get('t') == 'b': # Not a HA zone, and a beacon so assume mobile beacons = MOBILE_BEACONS_ACTIVE[dev_id] if location not in beacons: @@ -119,7 +123,7 @@ def setup_scanner(hass, config, see): if location not in regions: regions.append(location) _LOGGER.info("Enter region %s", location) - _set_gps_from_zone(kwargs, location, zone) + _set_gps_from_zone(kwargs, location, _zone) see(**kwargs) see_beacons(dev_id, kwargs) @@ -134,8 +138,8 @@ def setup_scanner(hass, config, see): if new_region: # Exit to previous region - zone = hass.states.get("zone.{}".format(new_region)) - _set_gps_from_zone(kwargs, new_region, zone) + _zone = hass.states.get("zone.{}".format(new_region)) + _set_gps_from_zone(kwargs, new_region, _zone) _LOGGER.info("Exit to %s", new_region) see(**kwargs) see_beacons(dev_id, kwargs) @@ -167,6 +171,23 @@ def setup_scanner(hass, config, see): data['event']) return + def owntracks_waypoint_update(topic, payload, qos): + """List of waypoints published by a user.""" + # Docs on available data: + # http://owntracks.org/booklet/tech/json/#_typewaypoints + data = validate_payload(payload, 'waypoints') + if not data: + return + + wayps = data['waypoints'] + _LOGGER.info("Got %d waypoints from %s", len(wayps), topic) + for wayp in wayps: + name = wayp['desc'] + lat = wayp['lat'] + lon = wayp['lon'] + rad = wayp['rad'] + zone.add_zone(hass, name, lat, lon, rad) + def see_beacons(dev_id, kwargs_param): """Set active beacons to the current location.""" kwargs = kwargs_param.copy() @@ -180,6 +201,10 @@ def setup_scanner(hass, config, see): mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1) mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1) + if waypoint_import_user is not None: + mqtt.subscribe(hass, WAYPOINT_TOPIC.format(waypoint_import_user), + owntracks_waypoint_update, 1) + return True @@ -200,12 +225,12 @@ def _parse_see_args(topic, data): return dev_id, kwargs -def _set_gps_from_zone(kwargs, location, zone): +def _set_gps_from_zone(kwargs, location, _zone): """Set the see parameters from the zone parameters.""" - if zone is not None: + if _zone is not None: kwargs['gps'] = ( - zone.attributes['latitude'], - zone.attributes['longitude']) - kwargs['gps_accuracy'] = zone.attributes['radius'] + _zone.attributes['latitude'], + _zone.attributes['longitude']) + kwargs['gps_accuracy'] = _zone.attributes['radius'] kwargs['location_name'] = location return kwargs diff --git a/homeassistant/components/zone.py b/homeassistant/components/zone.py index db57b387c9f..ee4ff8a48a6 100644 --- a/homeassistant/components/zone.py +++ b/homeassistant/components/zone.py @@ -27,7 +27,10 @@ ATTR_PASSIVE = 'passive' DEFAULT_PASSIVE = False ICON_HOME = 'mdi:home' +ICON_IMPORT = 'mdi:import' +entities = set() +_LOGGER = logging.getLogger(__name__) def active_zone(hass, latitude, longitude, radius=0): """Find the active zone for given latitude, longitude.""" @@ -70,7 +73,6 @@ def in_zone(zone, latitude, longitude, radius=0): def setup(hass, config): """Setup zone.""" - entities = set() for key in extract_domain_configs(config, DOMAIN): entries = config[key] @@ -90,7 +92,7 @@ def setup(hass, config): 'Each zone needs a latitude and longitude.') continue - zone = Zone(hass, name, latitude, longitude, radius, icon, passive) + zone = Zone(hass, name, latitude, longitude, radius, icon, passive, False) zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, entities) zone.update_ha_state() @@ -98,18 +100,30 @@ def setup(hass, config): if ENTITY_ID_HOME not in entities: zone = Zone(hass, hass.config.location_name, hass.config.latitude, - hass.config.longitude, DEFAULT_RADIUS, ICON_HOME, False) + hass.config.longitude, DEFAULT_RADIUS, ICON_HOME, False, False) zone.entity_id = ENTITY_ID_HOME zone.update_ha_state() return True +# Add a zone to the existing set +def add_zone(hass, name, latitude, longitude, radius): + _LOGGER.info("Adding new zone %s", name) + if name not in entities: + zone = Zone(hass, name, latitude, longitude, radius, ICON_IMPORT, + False, True) + zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, + entities) + zone.update_ha_state() + entities.add(zone.entity_id) + else: + _LOGGER.info("Zone already exists") class Zone(Entity): """Representation of a Zone.""" # pylint: disable=too-many-arguments, too-many-instance-attributes - def __init__(self, hass, name, latitude, longitude, radius, icon, passive): + def __init__(self, hass, name, latitude, longitude, radius, icon, passive, imported): """Initialize the zone.""" self.hass = hass self._name = name @@ -118,6 +132,7 @@ class Zone(Entity): self._radius = radius self._icon = icon self._passive = passive + self._imported = imported @property def name(self): From 2bea5a484f462da496b5e8a6c16a92721d46f487 Mon Sep 17 00:00:00 2001 From: NMA Date: Thu, 25 Aug 2016 16:47:34 +0530 Subject: [PATCH 02/13] Added test for Owntracks waypoints import --- .../components/device_tracker/owntracks.py | 3 +- .../device_tracker/test_owntracks.py | 37 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index 13cc918a436..9f505685721 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -32,6 +32,7 @@ LOCK = threading.Lock() CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy' CONF_WAYPOINT_IMPORT_USER = 'waypoint_import_user' + def setup_scanner(hass, config, see): """Setup an OwnTracks tracker.""" max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY) @@ -203,7 +204,7 @@ def setup_scanner(hass, config, see): if waypoint_import_user is not None: mqtt.subscribe(hass, WAYPOINT_TOPIC.format(waypoint_import_user), - owntracks_waypoint_update, 1) + owntracks_waypoint_update, 1) return True diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index 16fb1c4a4ce..f6f1fc58147 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -17,6 +17,7 @@ DEVICE = 'phone' LOCATION_TOPIC = "owntracks/{}/{}".format(USER, DEVICE) EVENT_TOPIC = "owntracks/{}/{}/event".format(USER, DEVICE) +WAYPOINT_TOPIC = 'owntracks/{}/{}/waypoint'.format(USER, DEVICE) DEVICE_TRACKER_STATE = "device_tracker.{}_{}".format(USER, DEVICE) @@ -24,6 +25,7 @@ IBEACON_DEVICE = 'keys' REGION_TRACKER_STATE = "device_tracker.beacon_{}".format(IBEACON_DEVICE) CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy' +CONF_WAYPOINT_IMPORT_USER = 'waypoint_import_user' LOCATION_MESSAGE = { 'batt': 92, @@ -107,6 +109,28 @@ REGION_LEAVE_INACCURATE_MESSAGE = { 'lat': 20.0, '_type': 'transition'} +WAYPOINTS_EXPORTED_MESSAGE = { + "_type": "waypoints", + "_creator": "test", + "waypoints": [ + { + "_type": "waypoint", + "tst": 3, + "lat": 47, + "lon": 9, + "rad": 10, + "desc": "exp_wayp1" + }, + { + "_type": "waypoint", + "tst": 4, + "lat": 3, + "lon": 9, + "rad": 500, + "desc": "exp_wayp2" + } + ] +} class TestDeviceTrackerOwnTracks(unittest.TestCase): """Test the OwnTrack sensor.""" @@ -118,7 +142,8 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): self.assertTrue(device_tracker.setup(self.hass, { device_tracker.DOMAIN: { CONF_PLATFORM: 'owntracks', - CONF_MAX_GPS_ACCURACY: 200 + CONF_MAX_GPS_ACCURACY: 200, + CONF_WAYPOINT_IMPORT_USER: USER }})) self.hass.states.set( @@ -486,3 +511,13 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): self.send_message(EVENT_TOPIC, exit_message) self.assertEqual(owntracks.MOBILE_BEACONS_ACTIVE['greg_phone'], []) + + def test_waypoint_import_simple(self): + """Test a simple import of list of waypoints.""" + waypoints_message = WAYPOINTS_EXPORTED_MESSAGE.copy() + self.send_message(WAYPOINT_TOPIC, waypoints_message) + # Check if it made it into states + wayp = self.hass.states.get('zone.exp_wayp1') + self.assertTrue(wayp != None) + wayp = self.hass.states.get('zone.exp_wayp2') + self.assertTrue(wayp != None) From 1ada7d621121a2d4ac7ade047032621b6de2ec30 Mon Sep 17 00:00:00 2001 From: NMA Date: Fri, 12 Aug 2016 14:48:28 +0530 Subject: [PATCH 03/13] Backend support for importing waypoints from owntracks as HA zones --- .../components/device_tracker/owntracks.py | 47 ++++++++++++++----- homeassistant/components/zone.py | 23 +++++++-- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index cdb1f90ba8a..9739643f884 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -12,6 +12,7 @@ from collections import defaultdict import homeassistant.components.mqtt as mqtt from homeassistant.const import STATE_HOME from homeassistant.util import convert, slugify +from homeassistant.components import zone DEPENDENCIES = ['mqtt'] @@ -22,6 +23,7 @@ BEACON_DEV_ID = 'beacon' LOCATION_TOPIC = 'owntracks/+/+' EVENT_TOPIC = 'owntracks/+/+/event' +WAYPOINT_TOPIC = 'owntracks/{}/+/waypoint' _LOGGER = logging.getLogger(__name__) @@ -32,10 +34,12 @@ CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy' VALIDATE_LOCATION = 'location' VALIDATE_TRANSITION = 'transition' +CONF_WAYPOINT_IMPORT_USER = 'waypoint_import_user' def setup_scanner(hass, config, see): """Setup an OwnTracks tracker.""" max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY) + waypoint_import_user = config.get(CONF_WAYPOINT_IMPORT_USER) def validate_payload(payload, data_type): """Validate OwnTracks payload.""" @@ -50,7 +54,7 @@ def setup_scanner(hass, config, see): 'because of missing or malformatted data: %s', data_type, data) return None - if data_type == VALIDATE_TRANSITION: + if data_type == VALIDATE_TRANSITION or data_type == 'waypoints': return data if max_gps_accuracy is not None and \ convert(data.get('acc'), float, 0.0) > max_gps_accuracy: @@ -110,9 +114,9 @@ def setup_scanner(hass, config, see): def enter_event(): """Execute enter event.""" - zone = hass.states.get("zone.{}".format(location)) + _zone = hass.states.get("zone.{}".format(location)) with LOCK: - if zone is None and data.get('t') == 'b': + if _zone is None and data.get('t') == 'b': # Not a HA zone, and a beacon so assume mobile beacons = MOBILE_BEACONS_ACTIVE[dev_id] if location not in beacons: @@ -124,7 +128,7 @@ def setup_scanner(hass, config, see): if location not in regions: regions.append(location) _LOGGER.info("Enter region %s", location) - _set_gps_from_zone(kwargs, location, zone) + _set_gps_from_zone(kwargs, location, _zone) see(**kwargs) see_beacons(dev_id, kwargs) @@ -139,8 +143,8 @@ def setup_scanner(hass, config, see): if new_region: # Exit to previous region - zone = hass.states.get("zone.{}".format(new_region)) - _set_gps_from_zone(kwargs, new_region, zone) + _zone = hass.states.get("zone.{}".format(new_region)) + _set_gps_from_zone(kwargs, new_region, _zone) _LOGGER.info("Exit to %s", new_region) see(**kwargs) see_beacons(dev_id, kwargs) @@ -182,6 +186,23 @@ def setup_scanner(hass, config, see): data['event']) return + def owntracks_waypoint_update(topic, payload, qos): + """List of waypoints published by a user.""" + # Docs on available data: + # http://owntracks.org/booklet/tech/json/#_typewaypoints + data = validate_payload(payload, 'waypoints') + if not data: + return + + wayps = data['waypoints'] + _LOGGER.info("Got %d waypoints from %s", len(wayps), topic) + for wayp in wayps: + name = wayp['desc'] + lat = wayp['lat'] + lon = wayp['lon'] + rad = wayp['rad'] + zone.add_zone(hass, name, lat, lon, rad) + def see_beacons(dev_id, kwargs_param): """Set active beacons to the current location.""" kwargs = kwargs_param.copy() @@ -195,6 +216,10 @@ def setup_scanner(hass, config, see): mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1) mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1) + if waypoint_import_user is not None: + mqtt.subscribe(hass, WAYPOINT_TOPIC.format(waypoint_import_user), + owntracks_waypoint_update, 1) + return True @@ -215,12 +240,12 @@ def _parse_see_args(topic, data): return dev_id, kwargs -def _set_gps_from_zone(kwargs, location, zone): +def _set_gps_from_zone(kwargs, location, _zone): """Set the see parameters from the zone parameters.""" - if zone is not None: + if _zone is not None: kwargs['gps'] = ( - zone.attributes['latitude'], - zone.attributes['longitude']) - kwargs['gps_accuracy'] = zone.attributes['radius'] + _zone.attributes['latitude'], + _zone.attributes['longitude']) + kwargs['gps_accuracy'] = _zone.attributes['radius'] kwargs['location_name'] = location return kwargs diff --git a/homeassistant/components/zone.py b/homeassistant/components/zone.py index db57b387c9f..ee4ff8a48a6 100644 --- a/homeassistant/components/zone.py +++ b/homeassistant/components/zone.py @@ -27,7 +27,10 @@ ATTR_PASSIVE = 'passive' DEFAULT_PASSIVE = False ICON_HOME = 'mdi:home' +ICON_IMPORT = 'mdi:import' +entities = set() +_LOGGER = logging.getLogger(__name__) def active_zone(hass, latitude, longitude, radius=0): """Find the active zone for given latitude, longitude.""" @@ -70,7 +73,6 @@ def in_zone(zone, latitude, longitude, radius=0): def setup(hass, config): """Setup zone.""" - entities = set() for key in extract_domain_configs(config, DOMAIN): entries = config[key] @@ -90,7 +92,7 @@ def setup(hass, config): 'Each zone needs a latitude and longitude.') continue - zone = Zone(hass, name, latitude, longitude, radius, icon, passive) + zone = Zone(hass, name, latitude, longitude, radius, icon, passive, False) zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, entities) zone.update_ha_state() @@ -98,18 +100,30 @@ def setup(hass, config): if ENTITY_ID_HOME not in entities: zone = Zone(hass, hass.config.location_name, hass.config.latitude, - hass.config.longitude, DEFAULT_RADIUS, ICON_HOME, False) + hass.config.longitude, DEFAULT_RADIUS, ICON_HOME, False, False) zone.entity_id = ENTITY_ID_HOME zone.update_ha_state() return True +# Add a zone to the existing set +def add_zone(hass, name, latitude, longitude, radius): + _LOGGER.info("Adding new zone %s", name) + if name not in entities: + zone = Zone(hass, name, latitude, longitude, radius, ICON_IMPORT, + False, True) + zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, + entities) + zone.update_ha_state() + entities.add(zone.entity_id) + else: + _LOGGER.info("Zone already exists") class Zone(Entity): """Representation of a Zone.""" # pylint: disable=too-many-arguments, too-many-instance-attributes - def __init__(self, hass, name, latitude, longitude, radius, icon, passive): + def __init__(self, hass, name, latitude, longitude, radius, icon, passive, imported): """Initialize the zone.""" self.hass = hass self._name = name @@ -118,6 +132,7 @@ class Zone(Entity): self._radius = radius self._icon = icon self._passive = passive + self._imported = imported @property def name(self): From e6b7511e7d40600655dd00d46e22d64b3807baad Mon Sep 17 00:00:00 2001 From: NMA Date: Thu, 25 Aug 2016 16:47:34 +0530 Subject: [PATCH 04/13] Added test for Owntracks waypoints import --- .../components/device_tracker/owntracks.py | 3 +- .../device_tracker/test_owntracks.py | 37 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index 9739643f884..93d217da5cf 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -36,6 +36,7 @@ VALIDATE_TRANSITION = 'transition' CONF_WAYPOINT_IMPORT_USER = 'waypoint_import_user' + def setup_scanner(hass, config, see): """Setup an OwnTracks tracker.""" max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY) @@ -218,7 +219,7 @@ def setup_scanner(hass, config, see): if waypoint_import_user is not None: mqtt.subscribe(hass, WAYPOINT_TOPIC.format(waypoint_import_user), - owntracks_waypoint_update, 1) + owntracks_waypoint_update, 1) return True diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index 393b61a3134..e998afdd9c0 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -17,6 +17,7 @@ DEVICE = 'phone' LOCATION_TOPIC = "owntracks/{}/{}".format(USER, DEVICE) EVENT_TOPIC = "owntracks/{}/{}/event".format(USER, DEVICE) +WAYPOINT_TOPIC = 'owntracks/{}/{}/waypoint'.format(USER, DEVICE) DEVICE_TRACKER_STATE = "device_tracker.{}_{}".format(USER, DEVICE) @@ -24,6 +25,7 @@ IBEACON_DEVICE = 'keys' REGION_TRACKER_STATE = "device_tracker.beacon_{}".format(IBEACON_DEVICE) CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy' +CONF_WAYPOINT_IMPORT_USER = 'waypoint_import_user' LOCATION_MESSAGE = { 'batt': 92, @@ -107,6 +109,28 @@ REGION_LEAVE_INACCURATE_MESSAGE = { 'lat': 20.0, '_type': 'transition'} +WAYPOINTS_EXPORTED_MESSAGE = { + "_type": "waypoints", + "_creator": "test", + "waypoints": [ + { + "_type": "waypoint", + "tst": 3, + "lat": 47, + "lon": 9, + "rad": 10, + "desc": "exp_wayp1" + }, + { + "_type": "waypoint", + "tst": 4, + "lat": 3, + "lon": 9, + "rad": 500, + "desc": "exp_wayp2" + } + ] +} REGION_ENTER_ZERO_MESSAGE = { 'lon': 1.0, @@ -143,7 +167,8 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): self.assertTrue(device_tracker.setup(self.hass, { device_tracker.DOMAIN: { CONF_PLATFORM: 'owntracks', - CONF_MAX_GPS_ACCURACY: 200 + CONF_MAX_GPS_ACCURACY: 200, + CONF_WAYPOINT_IMPORT_USER: USER }})) self.hass.states.set( @@ -530,3 +555,13 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): self.send_message(EVENT_TOPIC, exit_message) self.assertEqual(owntracks.MOBILE_BEACONS_ACTIVE['greg_phone'], []) + + def test_waypoint_import_simple(self): + """Test a simple import of list of waypoints.""" + waypoints_message = WAYPOINTS_EXPORTED_MESSAGE.copy() + self.send_message(WAYPOINT_TOPIC, waypoints_message) + # Check if it made it into states + wayp = self.hass.states.get('zone.exp_wayp1') + self.assertTrue(wayp != None) + wayp = self.hass.states.get('zone.exp_wayp2') + self.assertTrue(wayp != None) From 95b7a8c4b90397f3a606d128f30af110446311c0 Mon Sep 17 00:00:00 2001 From: NMA Date: Thu, 25 Aug 2016 17:07:53 +0530 Subject: [PATCH 05/13] Removed redundant assignment to CONF_WAYPOINT_IMPORT_USER --- homeassistant/components/device_tracker/owntracks.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index 612f9423a38..1e47ed6f83c 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -35,8 +35,6 @@ CONF_WAYPOINT_IMPORT_USER = 'waypoint_import_user' VALIDATE_LOCATION = 'location' VALIDATE_TRANSITION = 'transition' -CONF_WAYPOINT_IMPORT_USER = 'waypoint_import_user' - def setup_scanner(hass, config, see): """Setup an OwnTracks tracker.""" From 2ca3541eaceede2bb51e48eb5543a190f873e084 Mon Sep 17 00:00:00 2001 From: NMA Date: Thu, 25 Aug 2016 21:33:07 +0530 Subject: [PATCH 06/13] Fixed zone test break and code style issues --- .../components/device_tracker/owntracks.py | 24 +++++++++---------- homeassistant/components/zone.py | 14 +++++++---- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index 1e47ed6f83c..3d95e0e0268 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -12,7 +12,7 @@ from collections import defaultdict import homeassistant.components.mqtt as mqtt from homeassistant.const import STATE_HOME from homeassistant.util import convert, slugify -from homeassistant.components import zone +from homeassistant.components import zone as zone_comp DEPENDENCIES = ['mqtt'] @@ -114,9 +114,9 @@ def setup_scanner(hass, config, see): def enter_event(): """Execute enter event.""" - _zone = hass.states.get("zone.{}".format(location)) + zone = hass.states.get("zone.{}".format(location)) with LOCK: - if _zone is None and data.get('t') == 'b': + if zone is None and data.get('t') == 'b': # Not a HA zone, and a beacon so assume mobile beacons = MOBILE_BEACONS_ACTIVE[dev_id] if location not in beacons: @@ -128,7 +128,7 @@ def setup_scanner(hass, config, see): if location not in regions: regions.append(location) _LOGGER.info("Enter region %s", location) - _set_gps_from_zone(kwargs, location, _zone) + _set_gps_from_zone(kwargs, location, zone) see(**kwargs) see_beacons(dev_id, kwargs) @@ -143,8 +143,8 @@ def setup_scanner(hass, config, see): if new_region: # Exit to previous region - _zone = hass.states.get("zone.{}".format(new_region)) - _set_gps_from_zone(kwargs, new_region, _zone) + zone = hass.states.get("zone.{}".format(new_region)) + _set_gps_from_zone(kwargs, new_region, zone) _LOGGER.info("Exit to %s", new_region) see(**kwargs) see_beacons(dev_id, kwargs) @@ -201,7 +201,7 @@ def setup_scanner(hass, config, see): lat = wayp['lat'] lon = wayp['lon'] rad = wayp['rad'] - zone.add_zone(hass, name, lat, lon, rad) + zone_comp.add_zone(hass, name, lat, lon, rad) def see_beacons(dev_id, kwargs_param): """Set active beacons to the current location.""" @@ -240,12 +240,12 @@ def _parse_see_args(topic, data): return dev_id, kwargs -def _set_gps_from_zone(kwargs, location, _zone): +def _set_gps_from_zone(kwargs, location, zone): """Set the see parameters from the zone parameters.""" - if _zone is not None: + if zone is not None: kwargs['gps'] = ( - _zone.attributes['latitude'], - _zone.attributes['longitude']) - kwargs['gps_accuracy'] = _zone.attributes['radius'] + zone.attributes['latitude'], + zone.attributes['longitude']) + kwargs['gps_accuracy'] = zone.attributes['radius'] kwargs['location_name'] = location return kwargs diff --git a/homeassistant/components/zone.py b/homeassistant/components/zone.py index ee4ff8a48a6..ba63de00823 100644 --- a/homeassistant/components/zone.py +++ b/homeassistant/components/zone.py @@ -73,7 +73,7 @@ def in_zone(zone, latitude, longitude, radius=0): def setup(hass, config): """Setup zone.""" - + entities = set() for key in extract_domain_configs(config, DOMAIN): entries = config[key] if not isinstance(entries, list): @@ -92,7 +92,8 @@ def setup(hass, config): 'Each zone needs a latitude and longitude.') continue - zone = Zone(hass, name, latitude, longitude, radius, icon, passive, False) + zone = Zone(hass, name, latitude, longitude, radius, icon, + passive, False) zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, entities) zone.update_ha_state() @@ -100,7 +101,8 @@ def setup(hass, config): if ENTITY_ID_HOME not in entities: zone = Zone(hass, hass.config.location_name, hass.config.latitude, - hass.config.longitude, DEFAULT_RADIUS, ICON_HOME, False, False) + hass.config.longitude, DEFAULT_RADIUS, ICON_HOME, + False, False) zone.entity_id = ENTITY_ID_HOME zone.update_ha_state() @@ -108,12 +110,13 @@ def setup(hass, config): # Add a zone to the existing set def add_zone(hass, name, latitude, longitude, radius): + """Add a zone from other components""" _LOGGER.info("Adding new zone %s", name) if name not in entities: zone = Zone(hass, name, latitude, longitude, radius, ICON_IMPORT, False, True) zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, - entities) + entities) zone.update_ha_state() entities.add(zone.entity_id) else: @@ -123,7 +126,8 @@ class Zone(Entity): """Representation of a Zone.""" # pylint: disable=too-many-arguments, too-many-instance-attributes - def __init__(self, hass, name, latitude, longitude, radius, icon, passive, imported): + def __init__(self, hass, name, latitude, longitude, radius, icon, passive, + imported): """Initialize the zone.""" self.hass = hass self._name = name From ca73295dd1d5978357d884dc813425980dc99abd Mon Sep 17 00:00:00 2001 From: NMA Date: Thu, 25 Aug 2016 21:35:04 +0530 Subject: [PATCH 07/13] Fixed style issues --- homeassistant/components/zone.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/homeassistant/components/zone.py b/homeassistant/components/zone.py index ba63de00823..a32947d180c 100644 --- a/homeassistant/components/zone.py +++ b/homeassistant/components/zone.py @@ -32,6 +32,7 @@ ICON_IMPORT = 'mdi:import' entities = set() _LOGGER = logging.getLogger(__name__) + def active_zone(hass, latitude, longitude, radius=0): """Find the active zone for given latitude, longitude.""" # Sort entity IDs so that we are deterministic if equal distance to 2 zones @@ -108,6 +109,7 @@ def setup(hass, config): return True + # Add a zone to the existing set def add_zone(hass, name, latitude, longitude, radius): """Add a zone from other components""" @@ -122,6 +124,7 @@ def add_zone(hass, name, latitude, longitude, radius): else: _LOGGER.info("Zone already exists") + class Zone(Entity): """Representation of a Zone.""" From 47a9313fdb4daf67c4c053cf8ef8c0b1a84f58dd Mon Sep 17 00:00:00 2001 From: NMA Date: Thu, 25 Aug 2016 22:15:31 +0530 Subject: [PATCH 08/13] Fixed variable scope issues for entities --- homeassistant/components/zone.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zone.py b/homeassistant/components/zone.py index a32947d180c..fa095399920 100644 --- a/homeassistant/components/zone.py +++ b/homeassistant/components/zone.py @@ -29,7 +29,6 @@ DEFAULT_PASSIVE = False ICON_HOME = 'mdi:home' ICON_IMPORT = 'mdi:import' -entities = set() _LOGGER = logging.getLogger(__name__) @@ -112,9 +111,11 @@ def setup(hass, config): # Add a zone to the existing set def add_zone(hass, name, latitude, longitude, radius): - """Add a zone from other components""" + """Add a zone from other components.""" _LOGGER.info("Adding new zone %s", name) - if name not in entities: + entities = set() + + if hass.states.get('zone.' + name) is None: zone = Zone(hass, name, latitude, longitude, radius, ICON_IMPORT, False, True) zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, @@ -124,7 +125,6 @@ def add_zone(hass, name, latitude, longitude, radius): else: _LOGGER.info("Zone already exists") - class Zone(Entity): """Representation of a Zone.""" From ed872f6054e91ad18559f9380d18ee64a27a9a1e Mon Sep 17 00:00:00 2001 From: NMA Date: Thu, 25 Aug 2016 22:29:16 +0530 Subject: [PATCH 09/13] Fixed E302 --- homeassistant/components/zone.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/zone.py b/homeassistant/components/zone.py index fa095399920..d097b0b76ef 100644 --- a/homeassistant/components/zone.py +++ b/homeassistant/components/zone.py @@ -125,6 +125,7 @@ def add_zone(hass, name, latitude, longitude, radius): else: _LOGGER.info("Zone already exists") + class Zone(Entity): """Representation of a Zone.""" From 5a25c7427638594bc730569d14c29b5d3ad5c91a Mon Sep 17 00:00:00 2001 From: NMA Date: Fri, 26 Aug 2016 19:52:08 +0530 Subject: [PATCH 10/13] Refactored zone creation based on code review feedback, enhanced configuration --- .../components/device_tracker/owntracks.py | 33 +++++++++++++------ homeassistant/components/zone.py | 33 ++++++++++--------- .../device_tracker/test_owntracks.py | 21 ++++++++++-- 3 files changed, 59 insertions(+), 28 deletions(-) diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index 3d95e0e0268..caf0f38ad7f 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -23,23 +23,28 @@ BEACON_DEV_ID = 'beacon' LOCATION_TOPIC = 'owntracks/+/+' EVENT_TOPIC = 'owntracks/+/+/event' -WAYPOINT_TOPIC = 'owntracks/{}/+/waypoint' +WAYPOINT_TOPIC = 'owntracks/{}/{}/waypoint' _LOGGER = logging.getLogger(__name__) LOCK = threading.Lock() CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy' -CONF_WAYPOINT_IMPORT_USER = 'waypoint_import_user' +CONF_WAYPOINT_IMPORT = 'waypoints' +CONF_WAYPOINT_WHITELIST = 'waypoint_whitelist' VALIDATE_LOCATION = 'location' VALIDATE_TRANSITION = 'transition' +WAYPOINT_LAT_KEY = 'lat' +WAYPOINT_LON_KEY = 'lon' + def setup_scanner(hass, config, see): """Setup an OwnTracks tracker.""" max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY) - waypoint_import_user = config.get(CONF_WAYPOINT_IMPORT_USER) + waypoint_import = config.get(CONF_WAYPOINT_IMPORT, True) + waypoint_whitelist = config.get(CONF_WAYPOINT_WHITELIST) def validate_payload(payload, data_type): """Validate OwnTracks payload.""" @@ -198,10 +203,12 @@ def setup_scanner(hass, config, see): _LOGGER.info("Got %d waypoints from %s", len(wayps), topic) for wayp in wayps: name = wayp['desc'] - lat = wayp['lat'] - lon = wayp['lon'] + lat = wayp[WAYPOINT_LAT_KEY] + lon = wayp[WAYPOINT_LON_KEY] rad = wayp['rad'] - zone_comp.add_zone(hass, name, lat, lon, rad) + zone = zone_comp.Zone(hass, name, lat, lon, rad, + zone_comp.ICON_IMPORT, False, True) + zone_comp.add_zone(hass, name, zone) def see_beacons(dev_id, kwargs_param): """Set active beacons to the current location.""" @@ -216,9 +223,15 @@ def setup_scanner(hass, config, see): mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1) mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1) - if waypoint_import_user is not None: - mqtt.subscribe(hass, WAYPOINT_TOPIC.format(waypoint_import_user), - owntracks_waypoint_update, 1) + if waypoint_import: + if waypoint_whitelist is None: + mqtt.subscribe(hass, WAYPOINT_TOPIC.format('+', '+'), + owntracks_waypoint_update, 1) + else: + for whitelist_user in waypoint_whitelist: + mqtt.subscribe(hass, WAYPOINT_TOPIC.format(whitelist_user, + '+'), + owntracks_waypoint_update, 1) return True @@ -231,7 +244,7 @@ def _parse_see_args(topic, data): kwargs = { 'dev_id': dev_id, 'host_name': host_name, - 'gps': (data['lat'], data['lon']) + 'gps': (data[WAYPOINT_LAT_KEY], data[WAYPOINT_LON_KEY]) } if 'acc' in data: kwargs['gps_accuracy'] = data['acc'] diff --git a/homeassistant/components/zone.py b/homeassistant/components/zone.py index d097b0b76ef..a9ad05434cd 100644 --- a/homeassistant/components/zone.py +++ b/homeassistant/components/zone.py @@ -92,17 +92,16 @@ def setup(hass, config): 'Each zone needs a latitude and longitude.') continue - zone = Zone(hass, name, latitude, longitude, radius, icon, - passive, False) - zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, - entities) - zone.update_ha_state() + zone = Zone(hass, name, latitude, longitude, radius, + icon, passive, False) + add_zone(hass, name, zone, entities) entities.add(zone.entity_id) if ENTITY_ID_HOME not in entities: - zone = Zone(hass, hass.config.location_name, hass.config.latitude, - hass.config.longitude, DEFAULT_RADIUS, ICON_HOME, - False, False) + zone = Zone(hass, hass.config.location_name, + hass.config.latitude, hass.config.longitude, + DEFAULT_RADIUS, ICON_HOME, False, False) + add_zone(hass, hass.config.location_name, zone, entities) zone.entity_id = ENTITY_ID_HOME zone.update_ha_state() @@ -110,20 +109,24 @@ def setup(hass, config): # Add a zone to the existing set -def add_zone(hass, name, latitude, longitude, radius): +def add_zone(hass, name, zone, entities=None): """Add a zone from other components.""" _LOGGER.info("Adding new zone %s", name) - entities = set() + if entities is None: + _entities = set() + else: + _entities = entities - if hass.states.get('zone.' + name) is None: - zone = Zone(hass, name, latitude, longitude, radius, ICON_IMPORT, - False, True) + zone_exists = hass.states.get('zone.' + str(name)) + if zone_exists is None: zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, - entities) + _entities) zone.update_ha_state() - entities.add(zone.entity_id) + _entities.add(zone.entity_id) + return zone else: _LOGGER.info("Zone already exists") + return zone_exists class Zone(Entity): diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index 4abad8b6ca1..9a2e1e6d643 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -17,7 +17,10 @@ DEVICE = 'phone' LOCATION_TOPIC = "owntracks/{}/{}".format(USER, DEVICE) EVENT_TOPIC = "owntracks/{}/{}/event".format(USER, DEVICE) -WAYPOINT_TOPIC = 'owntracks/{}/{}/waypoint'.format(USER, DEVICE) +WAYPOINT_TOPIC = owntracks.WAYPOINT_TOPIC.format(USER, DEVICE) +USER_BLACKLIST = 'ram' +WAYPOINT_TOPIC_BLOCKED = owntracks.WAYPOINT_TOPIC.format(USER_BLACKLIST, + DEVICE) DEVICE_TRACKER_STATE = "device_tracker.{}_{}".format(USER, DEVICE) @@ -25,7 +28,8 @@ IBEACON_DEVICE = 'keys' REGION_TRACKER_STATE = "device_tracker.beacon_{}".format(IBEACON_DEVICE) CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy' -CONF_WAYPOINT_IMPORT_USER = 'waypoint_import_user' +CONF_WAYPOINT_IMPORT = owntracks.CONF_WAYPOINT_IMPORT +CONF_WAYPOINT_WHITELIST = owntracks.CONF_WAYPOINT_WHITELIST LOCATION_MESSAGE = { 'batt': 92, @@ -168,7 +172,8 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): device_tracker.DOMAIN: { CONF_PLATFORM: 'owntracks', CONF_MAX_GPS_ACCURACY: 200, - CONF_WAYPOINT_IMPORT_USER: USER + CONF_WAYPOINT_IMPORT: True, + CONF_WAYPOINT_WHITELIST: ['jon', 'greg'] }})) self.hass.states.set( @@ -565,3 +570,13 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): self.assertTrue(wayp is not None) wayp = self.hass.states.get('zone.exp_wayp2') self.assertTrue(wayp is not None) + + def test_waypoint_import_blacklist(self): + """Test import of list of waypoints for blacklisted user.""" + waypoints_message = WAYPOINTS_EXPORTED_MESSAGE.copy() + self.send_message(WAYPOINT_TOPIC_BLOCKED, waypoints_message) + # Check if it made it into states + wayp = self.hass.states.get('zone.exp_wayp1') + self.assertTrue(wayp is None) + wayp = self.hass.states.get('zone.exp_wayp2') + self.assertTrue(wayp is None) From 2430acf3ad486e92e64bb6d65299d8130c6b6fc7 Mon Sep 17 00:00:00 2001 From: NMA Date: Fri, 26 Aug 2016 22:00:48 +0530 Subject: [PATCH 11/13] Added unit test to enhance waypoint_whitelist coverage --- .../device_tracker/test_owntracks.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index 9a2e1e6d643..cd520daf0a4 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -217,6 +217,10 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): except FileNotFoundError: pass + def mock_see(**kwargs): + """Fake see method for owntracks.""" + return + def send_message(self, topic, message): """Test the sending of a message.""" fire_mqtt_message( @@ -580,3 +584,19 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): self.assertTrue(wayp is None) wayp = self.hass.states.get('zone.exp_wayp2') self.assertTrue(wayp is None) + + def test_waypoint_import_no_whitelist(self): + """Test import of list of waypoints with no whitelist set.""" + test_config = { + CONF_PLATFORM: 'owntracks', + CONF_MAX_GPS_ACCURACY: 200, + CONF_WAYPOINT_IMPORT: True + } + owntracks.setup_scanner(self.hass, test_config, self.mock_see) + waypoints_message = WAYPOINTS_EXPORTED_MESSAGE.copy() + self.send_message(WAYPOINT_TOPIC_BLOCKED, waypoints_message) + # Check if it made it into states + wayp = self.hass.states.get('zone.exp_wayp1') + self.assertTrue(wayp is not None) + wayp = self.hass.states.get('zone.exp_wayp2') + self.assertTrue(wayp is not None) From 70fe7f747a54f87e60248e2f9f3b242880dda241 Mon Sep 17 00:00:00 2001 From: NMA Date: Sun, 28 Aug 2016 13:18:30 +0530 Subject: [PATCH 12/13] * Improved zone naming in waypoint import * Added more test coverage for owntracks and zone --- .../components/device_tracker/owntracks.py | 22 ++++-- homeassistant/components/zone.py | 7 +- .../device_tracker/test_owntracks.py | 67 ++++++++++++++++--- 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index caf0f38ad7f..c895537e5ff 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -203,12 +203,13 @@ def setup_scanner(hass, config, see): _LOGGER.info("Got %d waypoints from %s", len(wayps), topic) for wayp in wayps: name = wayp['desc'] + pretty_name = parse_topic(topic, True)[1] + ' - ' + name lat = wayp[WAYPOINT_LAT_KEY] lon = wayp[WAYPOINT_LON_KEY] rad = wayp['rad'] - zone = zone_comp.Zone(hass, name, lat, lon, rad, + zone = zone_comp.Zone(hass, pretty_name, lat, lon, rad, zone_comp.ICON_IMPORT, False, True) - zone_comp.add_zone(hass, name, zone) + zone_comp.add_zone(hass, pretty_name, zone) def see_beacons(dev_id, kwargs_param): """Set active beacons to the current location.""" @@ -236,11 +237,22 @@ def setup_scanner(hass, config, see): return True +def parse_topic(topic, pretty=False): + """Parse an MQTT topic owntracks/user/dev, return (user, dev) tuple.""" + parts = topic.split('/') + dev_id_format = '' + if pretty: + dev_id_format = '{} {}' + else: + dev_id_format = '{}_{}' + dev_id = slugify(dev_id_format.format(parts[1], parts[2])) + host_name = parts[1] + return (host_name, dev_id) + + def _parse_see_args(topic, data): """Parse the OwnTracks location parameters, into the format see expects.""" - parts = topic.split('/') - dev_id = slugify('{}_{}'.format(parts[1], parts[2])) - host_name = parts[1] + (host_name, dev_id) = parse_topic(topic, False) kwargs = { 'dev_id': dev_id, 'host_name': host_name, diff --git a/homeassistant/components/zone.py b/homeassistant/components/zone.py index a9ad05434cd..a7841578e2b 100644 --- a/homeassistant/components/zone.py +++ b/homeassistant/components/zone.py @@ -116,11 +116,10 @@ def add_zone(hass, name, zone, entities=None): _entities = set() else: _entities = entities - - zone_exists = hass.states.get('zone.' + str(name)) + zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, + _entities) + zone_exists = hass.states.get(zone.entity_id) if zone_exists is None: - zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, - _entities) zone.update_ha_state() _entities.add(zone.entity_id) return zone diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index cd520daf0a4..57125d6e6ea 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -136,6 +136,26 @@ WAYPOINTS_EXPORTED_MESSAGE = { ] } +WAYPOINTS_UPDATED_MESSAGE = { + "_type": "waypoints", + "_creator": "test", + "waypoints": [ + { + "_type": "waypoint", + "tst": 4, + "lat": 9, + "lon": 47, + "rad": 50, + "desc": "exp_wayp1" + }, + ] +} + +WAYPOINT_ENTITY_NAMES = ['zone.greg_phone__exp_wayp1', + 'zone.greg_phone__exp_wayp2', + 'zone.ram_phone__exp_wayp1', + 'zone.ram_phone__exp_wayp2'] + REGION_ENTER_ZERO_MESSAGE = { 'lon': 1.0, 'event': 'enter', @@ -160,6 +180,9 @@ REGION_LEAVE_ZERO_MESSAGE = { 'lat': 20.0, '_type': 'transition'} +BAD_JSON_PREFIX = '--$this is bad json#--' +BAD_JSON_SUFFIX = '** and it ends here ^^' + class TestDeviceTrackerOwnTracks(unittest.TestCase): """Test the OwnTrack sensor.""" @@ -221,10 +244,14 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): """Fake see method for owntracks.""" return - def send_message(self, topic, message): + def send_message(self, topic, message, corrupt=False): """Test the sending of a message.""" - fire_mqtt_message( - self.hass, topic, json.dumps(message)) + str_message = json.dumps(message) + if corrupt: + mod_message = BAD_JSON_PREFIX + str_message + BAD_JSON_SUFFIX + else: + mod_message = str_message + fire_mqtt_message(self.hass, topic, mod_message) self.hass.pool.block_till_done() def assert_location_state(self, location): @@ -570,9 +597,9 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): waypoints_message = WAYPOINTS_EXPORTED_MESSAGE.copy() self.send_message(WAYPOINT_TOPIC, waypoints_message) # Check if it made it into states - wayp = self.hass.states.get('zone.exp_wayp1') + wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[0]) self.assertTrue(wayp is not None) - wayp = self.hass.states.get('zone.exp_wayp2') + wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[1]) self.assertTrue(wayp is not None) def test_waypoint_import_blacklist(self): @@ -580,9 +607,9 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): waypoints_message = WAYPOINTS_EXPORTED_MESSAGE.copy() self.send_message(WAYPOINT_TOPIC_BLOCKED, waypoints_message) # Check if it made it into states - wayp = self.hass.states.get('zone.exp_wayp1') + wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[2]) self.assertTrue(wayp is None) - wayp = self.hass.states.get('zone.exp_wayp2') + wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[3]) self.assertTrue(wayp is None) def test_waypoint_import_no_whitelist(self): @@ -596,7 +623,29 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): waypoints_message = WAYPOINTS_EXPORTED_MESSAGE.copy() self.send_message(WAYPOINT_TOPIC_BLOCKED, waypoints_message) # Check if it made it into states - wayp = self.hass.states.get('zone.exp_wayp1') + wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[2]) self.assertTrue(wayp is not None) - wayp = self.hass.states.get('zone.exp_wayp2') + wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[3]) self.assertTrue(wayp is not None) + + def test_waypoint_import_bad_json(self): + """Test importing a bad JSON payload.""" + waypoints_message = WAYPOINTS_EXPORTED_MESSAGE.copy() + self.send_message(WAYPOINT_TOPIC, waypoints_message, True) + # Check if it made it into states + wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[2]) + self.assertTrue(wayp is None) + wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[3]) + self.assertTrue(wayp is None) + + def test_waypoint_import_existing(self): + """Test importing a zone that exists.""" + waypoints_message = WAYPOINTS_EXPORTED_MESSAGE.copy() + self.send_message(WAYPOINT_TOPIC, waypoints_message) + # Get the first waypoint exported + wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[0]) + # Send an update + waypoints_message = WAYPOINTS_UPDATED_MESSAGE.copy() + self.send_message(WAYPOINT_TOPIC, waypoints_message) + new_wayp = self.hass.states.get(WAYPOINT_ENTITY_NAMES[0]) + self.assertTrue(wayp == new_wayp) From 12e2c38436d0ebe4ae6302bf311578cac831bacd Mon Sep 17 00:00:00 2001 From: NMA Date: Wed, 31 Aug 2016 08:16:01 +0530 Subject: [PATCH 13/13] Code review feedback from @pavoni --- homeassistant/components/device_tracker/owntracks.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index c895537e5ff..abc503a370a 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -35,6 +35,7 @@ CONF_WAYPOINT_WHITELIST = 'waypoint_whitelist' VALIDATE_LOCATION = 'location' VALIDATE_TRANSITION = 'transition' +VALIDATE_WAYPOINTS = 'waypoints' WAYPOINT_LAT_KEY = 'lat' WAYPOINT_LON_KEY = 'lon' @@ -59,7 +60,7 @@ def setup_scanner(hass, config, see): 'because of missing or malformatted data: %s', data_type, data) return None - if data_type == VALIDATE_TRANSITION or data_type == 'waypoints': + if data_type == VALIDATE_TRANSITION or data_type == VALIDATE_WAYPOINTS: return data if max_gps_accuracy is not None and \ convert(data.get('acc'), float, 0.0) > max_gps_accuracy: @@ -195,7 +196,7 @@ def setup_scanner(hass, config, see): """List of waypoints published by a user.""" # Docs on available data: # http://owntracks.org/booklet/tech/json/#_typewaypoints - data = validate_payload(payload, 'waypoints') + data = validate_payload(payload, VALIDATE_WAYPOINTS) if not data: return