diff --git a/homeassistant/components/proximity.py b/homeassistant/components/proximity.py index ba0a192398f..fceec21dd5d 100644 --- a/homeassistant/components/proximity.py +++ b/homeassistant/components/proximity.py @@ -9,106 +9,92 @@ https://home-assistant.io/components/proximity/ """ import logging +import voluptuous as vol + +from homeassistant.const import ( + CONF_ZONE, CONF_DEVICES, CONF_UNIT_OF_MEASUREMENT) from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import track_state_change -from homeassistant.util.location import distance from homeassistant.util.distance import convert -from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT - -DEPENDENCIES = ['zone', 'device_tracker'] - -DOMAIN = 'proximity' - -NOT_SET = 'not set' - -# Default tolerance -DEFAULT_TOLERANCE = 1 - -# Default zone -DEFAULT_PROXIMITY_ZONE = 'home' - -# Default distance to zone -DEFAULT_DIST_TO_ZONE = NOT_SET - -# Default direction of travel -DEFAULT_DIR_OF_TRAVEL = NOT_SET - -# Default nearest device -DEFAULT_NEAREST = NOT_SET - -# Entity attributes -ATTR_DIST_FROM = 'dist_to_zone' -ATTR_DIR_OF_TRAVEL = 'dir_of_travel' -ATTR_NEAREST = 'nearest' +from homeassistant.util.location import distance +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) +ATTR_DIR_OF_TRAVEL = 'dir_of_travel' +ATTR_DIST_FROM = 'dist_to_zone' +ATTR_NEAREST = 'nearest' -def setup_proximity_component(hass, config): +CONF_IGNORED_ZONES = 'ignored_zones' +CONF_TOLERANCE = 'tolerance' + +DEFAULT_DIR_OF_TRAVEL = 'not set' +DEFAULT_DIST_TO_ZONE = 'not set' +DEFAULT_NEAREST = 'not set' +DEFAULT_PROXIMITY_ZONE = 'home' +DEFAULT_TOLERANCE = 1 +DEPENDENCIES = ['zone', 'device_tracker'] +DOMAIN = 'proximity' + +UNITS = ['km', 'm', 'mi', 'ft'] + +ZONE_SCHEMA = vol.Schema({ + vol.Optional(CONF_ZONE, default=DEFAULT_PROXIMITY_ZONE): cv.string, + vol.Optional(CONF_DEVICES, default=[]): + vol.All(cv.ensure_list, [cv.entity_id]), + vol.Optional(CONF_IGNORED_ZONES, default=[]): + vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_TOLERANCE, default=DEFAULT_TOLERANCE): cv.positive_int, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): vol.All(cv.string, vol.In(UNITS)), +}) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + cv.slug: ZONE_SCHEMA, + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup_proximity_component(hass, name, config): """Set up individual proximity component.""" - # Get the devices from configuration.yaml. - if 'devices' not in config: - _LOGGER.error('devices not found in config') - return False - - ignored_zones = [] - if 'ignored_zones' in config: - for variable in config['ignored_zones']: - ignored_zones.append(variable) - - proximity_devices = [] - for variable in config['devices']: - proximity_devices.append(variable) - - # Get the direction of travel tolerance from configuration.yaml. - tolerance = config.get('tolerance', DEFAULT_TOLERANCE) - - # Get the zone to monitor proximity to from configuration.yaml. - proximity_zone = config.get('zone', DEFAULT_PROXIMITY_ZONE) - - # Get the unit of measurement from configuration.yaml. - unit_of_measure = config.get(ATTR_UNIT_OF_MEASUREMENT, - hass.config.units.length_unit) - + ignored_zones = config.get(CONF_IGNORED_ZONES) + proximity_devices = config.get(CONF_DEVICES) + tolerance = config.get(CONF_TOLERANCE) + proximity_zone = name + unit_of_measurement = config.get( + CONF_UNIT_OF_MEASUREMENT, hass.config.units.length_unit) zone_id = 'zone.{}'.format(proximity_zone) - state = hass.states.get(zone_id) - zone_friendly_name = (state.name).lower() - proximity = Proximity(hass, zone_friendly_name, DEFAULT_DIST_TO_ZONE, + proximity = Proximity(hass, proximity_zone, DEFAULT_DIST_TO_ZONE, DEFAULT_DIR_OF_TRAVEL, DEFAULT_NEAREST, ignored_zones, proximity_devices, tolerance, - zone_id, unit_of_measure) + zone_id, unit_of_measurement) proximity.entity_id = '{}.{}'.format(DOMAIN, proximity_zone) proximity.update_ha_state() - # Main command to monitor proximity of devices. - track_state_change(hass, proximity_devices, - proximity.check_proximity_state_change) + track_state_change( + hass, proximity_devices, proximity.check_proximity_state_change) return True def setup(hass, config): """Get the zones and offsets from configuration.yaml.""" - result = True - if isinstance(config[DOMAIN], list): - for proximity_config in config[DOMAIN]: - if not setup_proximity_component(hass, proximity_config): - result = False - elif not setup_proximity_component(hass, config[DOMAIN]): - result = False + for zone, proximity_config in config[DOMAIN].items(): + setup_proximity_component(hass, zone, proximity_config) - return result + return True -class Proximity(Entity): # pylint: disable=too-many-instance-attributes +# pylint: disable=too-many-instance-attributes +class Proximity(Entity): """Representation of a Proximity.""" # pylint: disable=too-many-arguments def __init__(self, hass, zone_friendly_name, dist_to, dir_of_travel, nearest, ignored_zones, proximity_devices, tolerance, - proximity_zone, unit_of_measure): + proximity_zone, unit_of_measurement): """Initialize the proximity.""" self.hass = hass self.friendly_name = zone_friendly_name @@ -119,7 +105,7 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes self.proximity_devices = proximity_devices self.tolerance = tolerance self.proximity_zone = proximity_zone - self.unit_of_measure = unit_of_measure + self._unit_of_measurement = unit_of_measurement @property def name(self): @@ -134,7 +120,7 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes @property def unit_of_measurement(self): """Return the unit of measurement of this entity.""" - return self.unit_of_measure + return self._unit_of_measurement @property def state_attributes(self): @@ -209,7 +195,7 @@ class Proximity(Entity): # pylint: disable=too-many-instance-attributes # Add the device and distance to a dictionary. distances_to_zone[device] = round( - convert(dist_to_zone, 'm', self.unit_of_measure), 1) + convert(dist_to_zone, 'm', self.unit_of_measurement), 1) # Loop through each of the distances collected and work out the # closest. diff --git a/tests/components/test_proximity.py b/tests/components/test_proximity.py index cbd36a1fc1f..1a1033ab31d 100644 --- a/tests/components/test_proximity.py +++ b/tests/components/test_proximity.py @@ -1,13 +1,17 @@ """The tests for the Proximity component.""" -from homeassistant.components import proximity +import unittest +from homeassistant.components import proximity +from homeassistant.components.proximity import DOMAIN + +from homeassistant.bootstrap import setup_component from tests.common import get_test_home_assistant -class TestProximity: +class TestProximity(unittest.TestCase): """Test the Proximity component.""" - def setup_method(self, method): + def setUp(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.states.set( @@ -27,31 +31,34 @@ class TestProximity: 'radius': 10 }) - def teardown_method(self, method): + def tearDown(self): """Stop everything that was started.""" self.hass.stop() def test_proximities(self): """Test a list of proximities.""" - assert proximity.setup(self.hass, { - 'proximity': [{ - 'zone': 'home', - 'ignored_zones': { - 'work' + config = { + 'proximity': { + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ], + 'tolerance': '1' }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' - }, - 'tolerance': '1' - }, { - 'zone': 'work', - 'devices': { - 'device_tracker.test1' - }, - 'tolerance': '1' - }] - }) + 'work': { + 'devices': [ + 'device_tracker.test1' + ], + 'tolerance': '1' + } + } + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) proximities = ['home', 'work'] @@ -66,40 +73,46 @@ class TestProximity: state = self.hass.states.get('proximity.' + prox) assert state.state == '0' - def test_proximities_missing_devices(self): - """Test a list of proximities with one missing devices.""" - assert not proximity.setup(self.hass, { - 'proximity': [{ - 'zone': 'home', - 'ignored_zones': { - 'work' + def test_proximities_setup(self): + """Test a list of proximities with missing devices.""" + config = { + 'proximity': { + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ], + 'tolerance': '1' }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' - }, - 'tolerance': '1' - }, { - 'zone': 'work', - 'tolerance': '1' - }] - }) + 'work': { + 'tolerance': '1' + } + } + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) def test_proximity(self): """Test the proximity.""" - assert proximity.setup(self.hass, { + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' - }, - 'tolerance': '1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ], + 'tolerance': '1' + } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) state = self.hass.states.get('proximity.home') assert state.state == 'not set' @@ -111,75 +124,23 @@ class TestProximity: state = self.hass.states.get('proximity.home') assert state.state == '0' - def test_no_devices_in_config(self): - """Test for missing devices in configuration.""" - assert not proximity.setup(self.hass, { - 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'tolerance': '1' - } - }) - - def test_no_tolerance_in_config(self): - """Test for missing tolerance in configuration .""" - 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): - """Test for ignored zones in configuration.""" - assert proximity.setup(self.hass, { - 'proximity': { - 'zone': 'home', - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' - }, - 'tolerance': '1' - } - }) - - def test_no_zone_in_config(self): - """Test for missing zone in configuration.""" - 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): """Test for tracker in zone.""" - assert proximity.setup(self.hass, { + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' - }, - 'tolerance': '1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1' + ], + 'tolerance': '1' + } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) self.hass.states.set( 'device_tracker.test1', 'home', @@ -196,19 +157,22 @@ class TestProximity: def test_device_trackers_in_zone(self): """Test for trackers in zone.""" - assert proximity.setup(self.hass, { + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' - }, - 'tolerance': '1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ], + 'tolerance': '1' + } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) self.hass.states.set( 'device_tracker.test1', 'home', @@ -234,18 +198,21 @@ class TestProximity: def test_device_tracker_test1_away(self): """Test for tracker state away.""" - assert proximity.setup(self.hass, { + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' - }, - 'tolerance': '1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + ], + 'tolerance': '1' + } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -254,6 +221,7 @@ class TestProximity: 'latitude': 20.1, 'longitude': 10.1 }) + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' @@ -261,17 +229,21 @@ class TestProximity: def test_device_tracker_test1_awayfurther(self): """Test for tracker state away further.""" - assert proximity.setup(self.hass, { + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + ], + 'tolerance': '1' } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -284,31 +256,6 @@ class TestProximity: 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.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): - """Test for tracker state away closer.""" - assert proximity.setup(self.hass, { - 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' - } - } - }) self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -320,32 +267,67 @@ class TestProximity: self.hass.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.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): - """Test for tracker in ignored zone.""" - assert proximity.setup(self.hass, { + def test_device_tracker_test1_awaycloser(self): + """Test for tracker state away closer.""" + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + ], + 'tolerance': '1' } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) + + self.hass.states.set( + 'device_tracker.test1', 'not_home', + { + 'friendly_name': 'test1', + 'latitude': 40.1, + 'longitude': 20.1 + }) + self.hass.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.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_all_device_trackers_in_ignored_zone(self): + """Test for tracker in ignored zone.""" + config = { + 'proximity': { + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + ], + 'tolerance': '1' + } + } + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) self.hass.states.set( 'device_tracker.test1', 'work', @@ -360,18 +342,21 @@ class TestProximity: def test_device_tracker_test1_no_coordinates(self): """Test for tracker with no coordinates.""" - assert proximity.setup(self.hass, { + config = { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' - }, - 'tolerance': '1' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + ], + 'tolerance': '1' + } } - }) + } + + self.assertTrue(setup_component(self.hass, DOMAIN, config)) self.hass.states.set( 'device_tracker.test1', 'not_home', @@ -397,15 +382,18 @@ class TestProximity: 'friendly_name': 'test2' }) self.hass.block_till_done() + assert proximity.setup(self.hass, { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ], + 'tolerance': '1', } } }) @@ -421,6 +409,7 @@ class TestProximity: 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', { @@ -449,13 +438,14 @@ class TestProximity: self.hass.block_till_done() assert proximity.setup(self.hass, { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ] } } }) @@ -471,6 +461,7 @@ class TestProximity: 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', { @@ -499,13 +490,14 @@ class TestProximity: self.hass.block_till_done() assert proximity.setup(self.hass, { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ] } } }) @@ -536,15 +528,17 @@ class TestProximity: 'friendly_name': 'test2' }) self.hass.block_till_done() + assert proximity.setup(self.hass, { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ] } } }) @@ -566,6 +560,7 @@ class TestProximity: 'longitude': 10.1 }) self.hass.block_till_done() + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -574,6 +569,7 @@ class TestProximity: 'longitude': 20.1 }) self.hass.block_till_done() + self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -582,12 +578,14 @@ class TestProximity: 'longitude': 15.1 }) self.hass.block_till_done() + self.hass.states.set( 'device_tracker.test1', 'work', { 'friendly_name': 'test1' }) self.hass.block_till_done() + state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test2' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -596,14 +594,15 @@ class TestProximity: """Test for tracker states.""" assert proximity.setup(self.hass, { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1' - }, - 'tolerance': 1000 + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1' + ], + 'tolerance': 1000 + } } }) @@ -618,6 +617,7 @@ class TestProximity: 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', { @@ -644,15 +644,17 @@ class TestProximity: 'friendly_name': 'test2' }) self.hass.block_till_done() + assert proximity.setup(self.hass, { 'proximity': { - 'zone': 'home', - 'ignored_zones': { - 'work' - }, - 'devices': { - 'device_tracker.test1', - 'device_tracker.test2' + 'home': { + 'ignored_zones': [ + 'work' + ], + 'devices': [ + 'device_tracker.test1', + 'device_tracker.test2' + ] } } })