From 0d2891ebccba54dbea9c16059dee177e64d51fa3 Mon Sep 17 00:00:00 2001 From: Nick Waring Date: Sun, 7 Feb 2016 08:51:21 +0000 Subject: [PATCH 1/2] Test file for the proximity component --- tests/components/test_proximity.py | 616 +++++++++++++++++++++++++++++ 1 file changed, 616 insertions(+) create mode 100644 tests/components/test_proximity.py diff --git a/tests/components/test_proximity.py b/tests/components/test_proximity.py new file mode 100644 index 00000000000..feca18a7a4e --- /dev/null +++ b/tests/components/test_proximity.py @@ -0,0 +1,616 @@ +""" +tests.components.proximity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests proximity component. +""" + +import homeassistant.core as ha +from homeassistant.components import proximity + +class TestProximity: + """ Test the Proximity component. """ + + def setup_method(self, method): + self.hass = ha.HomeAssistant() + self.hass.states.set( + 'zone.home', 'zoning', + { + 'name': 'home', + 'latitude': 2.1, + 'longitude': 1.1, + 'radius': 10 + }) + + def teardown_method(self, method): + """ Stop down stuff we started. """ + self.hass.stop() + + def test_proximity(self): + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1', + 'device_tracker.test2' + }, + 'tolerance': '1' + } + }) + + state = self.hass.states.get('proximity.home') + assert state.state == 'not set' + assert state.attributes.get('nearest') == 'not set' + assert state.attributes.get('dir_of_travel') == 'not set' + + self.hass.states.set('proximity.home', '0') + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.state == '0' + + def test_no_devices_in_config(self): + assert not proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'tolerance': '1' + } + }) + + def test_no_tolerance_in_config(self): + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1', + 'device_tracker.test2' + } + } + }) + + def test_no_ignored_zones_in_config(self): + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'devices': { + 'device_tracker.test1', + 'device_tracker.test2' + }, + 'tolerance': '1' + } + }) + + def test_no_zone_in_config(self): + assert proximity.setup(self.hass, { + 'proximity': { + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1', + 'device_tracker.test2' + }, + 'tolerance': '1' + } + }) + + def test_device_tracker_test1_in_zone(self): + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1' + }, + 'tolerance': '1' + } + }) + + self.hass.states.set( + 'device_tracker.test1', 'home', + { + 'friendly_name': 'test1', + 'latitude': 2.1, + 'longitude': 1.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.state == '0' + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'arrived' + + def test_device_trackers_in_zone(self): + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1', + 'device_tracker.test2' + }, + 'tolerance': '1' + } + }) + + self.hass.states.set( + 'device_tracker.test1', 'home', + { + 'friendly_name': 'test1', + 'latitude': 2.1, + 'longitude': 1.1 + }) + self.hass.pool.block_till_done() + self.hass.states.set( + 'device_tracker.test2', 'home', + { + 'friendly_name': 'test2', + 'latitude': 2.1, + 'longitude': 1.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.state == '0' + assert (state.attributes.get('nearest') == 'test1, test2') or (state.attributes.get('nearest') == 'test2, test1') + assert state.attributes.get('dir_of_travel') == 'arrived' + + def test_device_tracker_test1_away(self): + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1' + }, + 'tolerance': '1' + } + }) + + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 20.1, + 'longitude': 10.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'unknown' + + def test_device_tracker_test1_awayfurther(self): + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1' + } + } + }) + + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 20.1, + 'longitude': 10.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'unknown' + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 40.1, + 'longitude': 20.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'away_from' + + def test_device_tracker_test1_awaycloser(self): + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1' + } + } + }) + + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 40.1, + 'longitude': 20.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'unknown' + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 20.1, + 'longitude': 10.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'towards' + + def test_all_device_trackers_in_ignored_zone(self): + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1' + } + } + }) + + self.hass.states.set( + 'device_tracker.test1', 'work', + { + 'friendly_name': 'test1' + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.state == 'not set' + assert state.attributes.get('nearest') == 'not set' + assert state.attributes.get('dir_of_travel') == 'not set' + + def test_device_tracker_test1_no_coordinates(self): + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1' + }, + 'tolerance': '1' + } + }) + + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1' + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'not set' + assert state.attributes.get('dir_of_travel') == 'not set' + + def test_device_tracker_test1_awayfurther_than_test2_first_test1(self): + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1' + }) + self.hass.pool.block_till_done() + self.hass.states.set( + 'device_tracker.test2', 'not_home', + { + 'friendly_name': 'test2' + }) + self.hass.pool.block_till_done() + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1', + 'device_tracker.test2' + } + } + }) + + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 20.1, + 'longitude': 10.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'unknown' + self.hass.states.set( + 'device_tracker.test2', 'not_home', + { + 'friendly_name': 'test2', + 'latitude': 40.1, + 'longitude': 20.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'unknown' + + def test_device_tracker_test1_awayfurther_than_test2_first_test2(self): + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1' + }) + self.hass.pool.block_till_done() + self.hass.states.set( + 'device_tracker.test2', 'not_home', + { + 'friendly_name': 'test2' + }) + self.hass.pool.block_till_done() + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1', + 'device_tracker.test2' + } + } + }) + + self.hass.states.set( + 'device_tracker.test2', 'not_home', + { + 'friendly_name': 'test2', + 'latitude': 40.1, + 'longitude': 20.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test2' + assert state.attributes.get('dir_of_travel') == 'unknown' + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 20.1, + 'longitude': 10.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'unknown' + + def test_device_tracker_test1_awayfurther_test2_in_ignored_zone(self): + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1' + }) + self.hass.pool.block_till_done() + self.hass.states.set( + 'device_tracker.test2', 'work', + { + 'friendly_name': 'test2' + }) + self.hass.pool.block_till_done() + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1', + 'device_tracker.test2' + } + } + }) + + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 20.1, + 'longitude': 10.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'unknown' + + def test_device_tracker_test1_awayfurther_than_test2_first_test1_than_test2_than_test1(self): + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1' + }) + self.hass.pool.block_till_done() + self.hass.states.set( + 'device_tracker.test2', 'not_home', + { + 'friendly_name': 'test2' + }) + self.hass.pool.block_till_done() + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1', + 'device_tracker.test2' + } + } + }) + + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 10.1, + 'longitude': 5.1 + }) + self.hass.pool.block_till_done() + + self.hass.states.set( + 'device_tracker.test2', 'not_home', + { + 'friendly_name': 'test2', + 'latitude': 20.1, + 'longitude': 10.1 + }) + self.hass.pool.block_till_done() + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 40.1, + 'longitude': 20.1 + }) + self.hass.pool.block_till_done() + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 35.1, + 'longitude': 15.1 + }) + self.hass.pool.block_till_done() + self.hass.states.set( + 'device_tracker.test1', 'work', + { + 'friendly_name': 'test1' + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test2' + assert state.attributes.get('dir_of_travel') == 'unknown' + + def test_device_tracker_test1_awayfurther_a_bit(self): + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1' + }, + 'tolerance': 1000 + } + }) + + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 20.1000001, + 'longitude': 10.1000001 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'unknown' + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 20.1000002, + 'longitude': 10.1000002 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'stationary' + + def test_device_tracker_test1_nearest_after_test2_in_ignored_zone(self): + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1' + }) + self.hass.pool.block_till_done() + self.hass.states.set( + 'device_tracker.test2', 'not_home', + { + 'friendly_name': 'test2' + }) + self.hass.pool.block_till_done() + assert proximity.setup(self.hass, { + 'proximity': { + 'zone': 'home', + 'ignored_zones': { + 'work' + }, + 'devices': { + 'device_tracker.test1', + 'device_tracker.test2' + } + } + }) + + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 20.1, + 'longitude': 10.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'unknown' + + self.hass.states.set( + 'device_tracker.test2', 'not_home', + { + 'friendly_name': 'test2', + 'latitude': 10.1, + 'longitude': 5.1 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test2' + assert state.attributes.get('dir_of_travel') == 'unknown' + + self.hass.states.set( + 'device_tracker.test2', 'work', + { + 'friendly_name': 'test2', + 'latitude': 12.6, + 'longitude': 7.6 + }) + self.hass.pool.block_till_done() + state = self.hass.states.get('proximity.home') + assert state.attributes.get('nearest') == 'test1' + assert state.attributes.get('dir_of_travel') == 'unknown' From cd0cef640391211637368cc66ed4954872c3dae9 Mon Sep 17 00:00:00 2001 From: Nick Waring Date: Sun, 7 Feb 2016 08:52:32 +0000 Subject: [PATCH 2/2] Component to track the proximity of devices to a zone --- homeassistant/components/proximity.py | 282 ++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 homeassistant/components/proximity.py diff --git a/homeassistant/components/proximity.py b/homeassistant/components/proximity.py new file mode 100644 index 00000000000..fd2483a2e83 --- /dev/null +++ b/homeassistant/components/proximity.py @@ -0,0 +1,282 @@ +""" +custom_components.proximity +~~~~~~~~~~~~~~~~~~~~~~~~~ +Component to monitor the proximity of devices to a particular zone and the +direction of travel. The result is an entity created in HA which maintains +the proximity data + +This component is useful to reduce the number of automation rules required +when wanting to perform automations based on locations outside a particular +zone. The standard HA zone and state based triggers allow similar control +but the number of rules grows exponentially when factors such as direction +of travel need to be taken into account. Some examples of its use include: +- Increase thermostat temperature as you near home +- Decrease temperature the further away from home you travel + +The Proximity entity which is created has the following values: +state = distance from the monitored zone (in km) +dir_of_travel = direction of the closest device to the monitoed zone. Values + are: + 'not set' + 'arrived' + 'towards' + 'away_from' + 'unknown' + 'stationary' +dist_to_zone = distance from the monitored zone (in km) + +Use configuration.yaml to enable the user to easily tune a number of settings: +- Zone: the zone to which this component is measuring the distance to. Default + is the home zone +- Ignored Zones: where proximity is not calculated for a device (either the + device being monitored or ones being compared (e.g. work or school) +- Devices: a list of devices to compare location against to check closeness to + the configured zone +- Tolerance: the tolerance used to calculate the direction of travel in metres + (to filter out small GPS co-ordinate changes + +Logging levels debug, info and error are in use + +Example configuration.yaml entry: +proximity: + zone: home + ignored_zones: + - twork + - elschool + devices: + - device_tracker.nwaring_nickmobile + - device_tracker.eleanorsiphone + - device_tracker.tsiphone + tolerance: 50 +""" + +import logging +from homeassistant.helpers.event import track_state_change +from homeassistant.helpers.entity import Entity +from homeassistant.util.location import distance + +DEPENDENCIES = ['zone', 'device_tracker'] + +# domain for the component +DOMAIN = 'proximity' + +# default tolerance +DEFAULT_TOLERANCE = 1 + +# default zone +DEFAULT_PROXIMITY_ZONE = 'home' + +# entity attributes +ATTR_DIST_FROM = 'dist_to_zone' +ATTR_DIR_OF_TRAVEL = 'dir_of_travel' +ATTR_NEAREST = 'nearest' +ATTR_FRIENDLY_NAME = 'friendly_name' + +# Shortcut for the logger +_LOGGER = logging.getLogger(__name__) + + +def setup(hass, config): # pylint: disable=too-many-locals,too-many-statements + """ get the zones and offsets from configuration.yaml""" + ignored_zones = [] + if 'ignored_zones' in config[DOMAIN]: + for variable in config[DOMAIN]['ignored_zones']: + ignored_zones.append(variable) + + # get the devices from configuration.yaml + if 'devices' not in config[DOMAIN]: + _LOGGER.error('devices not found in config') + return False + + proximity_devices = [] + for variable in config[DOMAIN]['devices']: + proximity_devices.append(variable) + + # get the direction of travel tolerance from configuration.yaml + tolerance = config[DOMAIN].get('tolerance', DEFAULT_TOLERANCE) + + # get the zone to monitor proximity to from configuration.yaml + proximity_zone = config[DOMAIN].get('zone', DEFAULT_PROXIMITY_ZONE) + + entity_id = DOMAIN + '.' + proximity_zone + proximity_zone = 'zone.' + proximity_zone + + state = hass.states.get(proximity_zone) + zone_friendly_name = (state.name).lower() + + # set the default values + dist_to_zone = 'not set' + dir_of_travel = 'not set' + nearest = 'not set' + + proximity = Proximity(hass, zone_friendly_name, dist_to_zone, + dir_of_travel, nearest, ignored_zones, + proximity_devices, tolerance, proximity_zone) + proximity.entity_id = entity_id + + proximity.update_ha_state() + + # main command to monitor proximity of devices + track_state_change(hass, proximity_devices, + proximity.check_proximity_state_change) + + # Tells the bootstrapper that the component was successfully initialized + return True + + +class Proximity(Entity): # pylint: disable=too-many-instance-attributes + """ Represents a Proximity in Home Assistant. """ + def __init__(self, hass, zone_friendly_name, dist_to, dir_of_travel, + nearest, ignored_zones, proximity_devices, tolerance, + proximity_zone): + # pylint: disable=too-many-arguments + self.hass = hass + self.friendly_name = zone_friendly_name + self.dist_to = dist_to + self.dir_of_travel = dir_of_travel + self.nearest = nearest + self.ignored_zones = ignored_zones + self.proximity_devices = proximity_devices + self.tolerance = tolerance + self.proximity_zone = proximity_zone + + @property + def state(self): + return self.dist_to + + @property + def unit_of_measurement(self): + """ Unit of measurement of this entity """ + return "km" + + @property + def state_attributes(self): + return { + ATTR_DIR_OF_TRAVEL: self.dir_of_travel, + ATTR_NEAREST: self.nearest, + ATTR_FRIENDLY_NAME: self.friendly_name + } + + def check_proximity_state_change(self, entity, old_state, new_state): + # pylint: disable=too-many-branches,too-many-statements,too-many-locals + """ Function to perform the proximity checking """ + entity_name = new_state.name + devices_to_calculate = False + devices_in_zone = '' + + zone_state = self.hass.states.get(self.proximity_zone) + proximity_latitude = zone_state.attributes.get('latitude') + proximity_longitude = zone_state.attributes.get('longitude') + + # check for devices in the monitored zone + for device in self.proximity_devices: + device_state = self.hass.states.get(device) + + if device_state.state not in self.ignored_zones: + devices_to_calculate = True + + # check the location of all devices + if (device_state.state).lower() == (self.friendly_name).lower(): + device_friendly = device_state.name + if devices_in_zone != '': + devices_in_zone = devices_in_zone + ', ' + devices_in_zone = devices_in_zone + device_friendly + + # no-one to track so reset the entity + if not devices_to_calculate: + self.dist_to = 'not set' + self.dir_of_travel = 'not set' + self.nearest = 'not set' + self.update_ha_state() + return + + # at least one device is in the monitored zone so update the entity + if devices_in_zone != '': + self.dist_to = 0 + self.dir_of_travel = 'arrived' + self.nearest = devices_in_zone + self.update_ha_state() + return + + # we can't check proximity because latitude and longitude don't exist + if 'latitude' not in new_state.attributes: + return + + # collect distances to the zone for all devices + distances_to_zone = {} + for device in self.proximity_devices: + # ignore devices in an ignored zone + device_state = self.hass.states.get(device) + if device_state.state in self.ignored_zones: + continue + + # ignore devices if proximity cannot be calculated + if 'latitude' not in device_state.attributes: + continue + + # calculate the distance to the proximity zone + dist_to_zone = distance(proximity_latitude, + proximity_longitude, + device_state.attributes['latitude'], + device_state.attributes['longitude']) + + # add the device and distance to a dictionary + distances_to_zone[device] = round(dist_to_zone / 1000, 1) + + # loop through each of the distances collected and work out the closest + closest_device = '' + dist_to_zone = 1000000 + + for device in distances_to_zone: + if distances_to_zone[device] < dist_to_zone: + closest_device = device + dist_to_zone = distances_to_zone[device] + + # if the closest device is one of the other devices + if closest_device != entity: + self.dist_to = round(distances_to_zone[closest_device]) + self.dir_of_travel = 'unknown' + device_state = self.hass.states.get(closest_device) + self.nearest = device_state.name + self.update_ha_state() + return + + # stop if we cannot calculate the direction of travel (i.e. we don't + # have a previous state and a current LAT and LONG) + if old_state is None or 'latitude' not in old_state.attributes: + self.dist_to = round(distances_to_zone[entity]) + self.dir_of_travel = 'unknown' + self.nearest = entity_name + self.update_ha_state() + return + + # reset the variables + distance_travelled = 0 + + # calculate the distance travelled + old_distance = distance(proximity_latitude, proximity_longitude, + old_state.attributes['latitude'], + old_state.attributes['longitude']) + new_distance = distance(proximity_latitude, proximity_longitude, + new_state.attributes['latitude'], + new_state.attributes['longitude']) + distance_travelled = round(new_distance - old_distance, 1) + + # check for tolerance + if distance_travelled < self.tolerance * -1: + direction_of_travel = 'towards' + elif distance_travelled > self.tolerance: + direction_of_travel = 'away_from' + else: + direction_of_travel = 'stationary' + + # update the proximity entity + self.dist_to = round(dist_to_zone) + self.dir_of_travel = direction_of_travel + self.nearest = entity_name + self.update_ha_state() + _LOGGER.debug('proximity.%s update entity: distance=%s: direction=%s: ' + 'device=%s', self.friendly_name, round(dist_to_zone), + direction_of_travel, entity_name) + + _LOGGER.info('%s: proximity calculation complete', entity_name)