From 4a45510c882d7ccbcd764e7e11af81e117595eaf Mon Sep 17 00:00:00 2001 From: Victor Vostrikov <1998617+gorynychzmey@users.noreply.github.com> Date: Thu, 28 Feb 2019 01:40:23 +0100 Subject: [PATCH] Changed source priority for Person (#21479) * Added gps accuracy to Person * Corrected GPS accuracy for Person * Added priority of sources to Person * Fixed formatting * Removed rounding of coordinates. * Added test for source priority. Changed test for rounding of coordinates. * Improved code style * Code style cleanup * Code style cleanup * Code style cleanup * Code style cleanup * Code style cleanup * Lint * Lint --- homeassistant/components/person/__init__.py | 38 +++++++++++--- tests/components/person/test_init.py | 55 ++++++++++++++++----- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index f2bca91205c..c4af4a699cd 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -8,10 +8,11 @@ import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.components.device_tracker import ( - DOMAIN as DEVICE_TRACKER_DOMAIN) + DOMAIN as DEVICE_TRACKER_DOMAIN, ATTR_SOURCE_TYPE, SOURCE_TYPE_GPS) from homeassistant.const import ( - ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ID, CONF_NAME, - EVENT_HOMEASSISTANT_START, STATE_UNKNOWN, STATE_UNAVAILABLE) + ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_GPS_ACCURACY, + CONF_ID, CONF_NAME, EVENT_HOMEASSISTANT_START, + STATE_UNKNOWN, STATE_UNAVAILABLE, STATE_HOME, STATE_NOT_HOME) from homeassistant.core import callback, Event from homeassistant.auth import EVENT_USER_REMOVED import homeassistant.helpers.config_validation as cv @@ -285,6 +286,7 @@ class Person(RestoreEntity): self._editable = editable self._latitude = None self._longitude = None + self._gps_accuracy = None self._source = None self._state = None self._unsub_track_device = None @@ -315,9 +317,11 @@ class Person(RestoreEntity): ATTR_ID: self.unique_id, } if self._latitude is not None: - data[ATTR_LATITUDE] = round(self._latitude, 5) + data[ATTR_LATITUDE] = self._latitude if self._longitude is not None: - data[ATTR_LONGITUDE] = round(self._longitude, 5) + data[ATTR_LONGITUDE] = self._longitude + if self._gps_accuracy is not None: + data[ATTR_GPS_ACCURACY] = self._gps_accuracy if self._source is not None: data[ATTR_SOURCE] = self._source user_id = self._config.get(CONF_USER_ID) @@ -373,18 +377,34 @@ class Person(RestoreEntity): """Handle the device tracker state changes.""" self._update_state() + def _get_latest(self, prev, curr): + return curr \ + if prev is None or curr.last_updated > prev.last_updated \ + else prev + @callback def _update_state(self): """Update the state.""" - latest = None + latest_home = latest_not_home = latest_gps = latest = None for entity_id in self._config.get(CONF_DEVICE_TRACKERS, []): state = self.hass.states.get(entity_id) if not state or state.state in IGNORE_STATES: continue - if latest is None or state.last_updated > latest.last_updated: - latest = state + if state.attributes.get(ATTR_SOURCE_TYPE) == SOURCE_TYPE_GPS: + latest_gps = self._get_latest(latest_gps, state) + elif state.state == STATE_HOME: + latest_home = self._get_latest(latest_home, state) + elif state.state == STATE_NOT_HOME: + latest_not_home = self._get_latest(latest_not_home, state) + + if latest_home: + latest = latest_home + elif latest_gps: + latest = latest_gps + else: + latest = latest_not_home if latest: self._parse_source_state(latest) @@ -393,6 +413,7 @@ class Person(RestoreEntity): self._source = None self._latitude = None self._longitude = None + self._gps_accuracy = None self.async_schedule_update_ha_state() @@ -406,6 +427,7 @@ class Person(RestoreEntity): self._source = state.entity_id self._latitude = state.attributes.get(ATTR_LATITUDE) self._longitude = state.attributes.get(ATTR_LONGITUDE) + self._gps_accuracy = state.attributes.get(ATTR_GPS_ACCURACY) @websocket_api.websocket_command({ diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index 6c8c6ebd0dd..ef129a555be 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -4,8 +4,10 @@ from unittest.mock import Mock from homeassistant.components.person import ( ATTR_SOURCE, ATTR_USER_ID, DOMAIN, PersonManager) from homeassistant.const import ( - ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, STATE_UNKNOWN, - EVENT_HOMEASSISTANT_START) + ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_GPS_ACCURACY, + STATE_UNKNOWN, EVENT_HOMEASSISTANT_START) +from homeassistant.components.device_tracker import ( + ATTR_SOURCE_TYPE, SOURCE_TYPE_GPS, SOURCE_TYPE_ROUTER) from homeassistant.core import CoreState, State from homeassistant.setup import async_setup_component @@ -134,15 +136,18 @@ async def test_setup_tracker(hass, hass_admin_user): assert state.attributes.get(ATTR_USER_ID) == user_id hass.states.async_set( - DEVICE_TRACKER, 'not_home', - {ATTR_LATITUDE: 10.123456, ATTR_LONGITUDE: 11.123456}) + DEVICE_TRACKER, 'not_home', { + ATTR_LATITUDE: 10.123456, + ATTR_LONGITUDE: 11.123456, + ATTR_GPS_ACCURACY: 10}) await hass.async_block_till_done() state = hass.states.get('person.tracked_person') assert state.state == 'not_home' assert state.attributes.get(ATTR_ID) == '1234' - assert state.attributes.get(ATTR_LATITUDE) == 10.12346 - assert state.attributes.get(ATTR_LONGITUDE) == 11.12346 + assert state.attributes.get(ATTR_LATITUDE) == 10.123456 + assert state.attributes.get(ATTR_LONGITUDE) == 11.123456 + assert state.attributes.get(ATTR_GPS_ACCURACY) == 10 assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER assert state.attributes.get(ATTR_USER_ID) == user_id @@ -166,7 +171,8 @@ async def test_setup_two_trackers(hass, hass_admin_user): hass.bus.async_fire(EVENT_HOMEASSISTANT_START) await hass.async_block_till_done() - hass.states.async_set(DEVICE_TRACKER, 'home') + hass.states.async_set( + DEVICE_TRACKER, 'home', {ATTR_SOURCE_TYPE: SOURCE_TYPE_ROUTER}) await hass.async_block_till_done() state = hass.states.get('person.tracked_person') @@ -174,22 +180,49 @@ async def test_setup_two_trackers(hass, hass_admin_user): assert state.attributes.get(ATTR_ID) == '1234' assert state.attributes.get(ATTR_LATITUDE) is None assert state.attributes.get(ATTR_LONGITUDE) is None + assert state.attributes.get(ATTR_GPS_ACCURACY) is None assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER assert state.attributes.get(ATTR_USER_ID) == user_id hass.states.async_set( - DEVICE_TRACKER_2, 'not_home', - {ATTR_LATITUDE: 12.123456, ATTR_LONGITUDE: 13.123456}) + DEVICE_TRACKER_2, 'not_home', { + ATTR_LATITUDE: 12.123456, + ATTR_LONGITUDE: 13.123456, + ATTR_GPS_ACCURACY: 12, + ATTR_SOURCE_TYPE: SOURCE_TYPE_GPS}) + await hass.async_block_till_done() + hass.states.async_set( + DEVICE_TRACKER, 'not_home', {ATTR_SOURCE_TYPE: SOURCE_TYPE_ROUTER}) await hass.async_block_till_done() state = hass.states.get('person.tracked_person') assert state.state == 'not_home' assert state.attributes.get(ATTR_ID) == '1234' - assert state.attributes.get(ATTR_LATITUDE) == 12.12346 - assert state.attributes.get(ATTR_LONGITUDE) == 13.12346 + assert state.attributes.get(ATTR_LATITUDE) == 12.123456 + assert state.attributes.get(ATTR_LONGITUDE) == 13.123456 + assert state.attributes.get(ATTR_GPS_ACCURACY) == 12 assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER_2 assert state.attributes.get(ATTR_USER_ID) == user_id + hass.states.async_set( + DEVICE_TRACKER_2, 'zone1', {ATTR_SOURCE_TYPE: SOURCE_TYPE_GPS}) + await hass.async_block_till_done() + + state = hass.states.get('person.tracked_person') + assert state.state == 'zone1' + assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER_2 + + hass.states.async_set( + DEVICE_TRACKER, 'home', {ATTR_SOURCE_TYPE: SOURCE_TYPE_ROUTER}) + await hass.async_block_till_done() + hass.states.async_set( + DEVICE_TRACKER_2, 'zone2', {ATTR_SOURCE_TYPE: SOURCE_TYPE_GPS}) + await hass.async_block_till_done() + + state = hass.states.get('person.tracked_person') + assert state.state == 'home' + assert state.attributes.get(ATTR_SOURCE) == DEVICE_TRACKER + async def test_ignore_unavailable_states(hass, hass_admin_user): """Test set up person with two device trackers, one unavailable."""