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."""
-- 
GitLab