diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index df19d86bb5c..20014c3414b 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -15,7 +15,7 @@ from homeassistant.components.device_tracker import ( DOMAIN as DEVICE_TRACKER_DOMAIN) from homeassistant.const import ( ATTR_ID, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_ID, CONF_NAME, - EVENT_HOMEASSISTANT_START) + EVENT_HOMEASSISTANT_START, STATE_UNKNOWN, STATE_UNAVAILABLE) from homeassistant.core import callback, Event from homeassistant.auth import EVENT_USER_REMOVED import homeassistant.helpers.config_validation as cv @@ -25,7 +25,6 @@ from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.components import websocket_api from homeassistant.helpers.typing import HomeAssistantType, ConfigType -from homeassistant.util import dt as dt_util from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) @@ -38,6 +37,8 @@ DOMAIN = 'person' STORAGE_KEY = DOMAIN STORAGE_VERSION = 1 SAVE_DELAY = 10 +# Device tracker states to ignore +IGNORE_STATES = (STATE_UNKNOWN, STATE_UNAVAILABLE) PERSON_SCHEMA = vol.Schema({ vol.Required(CONF_ID): cv.string, @@ -350,30 +351,31 @@ class Person(RestoreEntity): trackers = self._config.get(CONF_DEVICE_TRACKERS) if trackers: - def sort_key(state): - if state: - return state.last_updated - return dt_util.utc_from_timestamp(0) - - latest = max( - [self.hass.states.get(entity_id) for entity_id in trackers], - key=sort_key - ) - - @callback - def async_handle_tracker_update(entity, old_state, new_state): - """Handle the device tracker state changes.""" - self._parse_source_state(new_state) - self.async_schedule_update_ha_state() - _LOGGER.debug( "Subscribe to device trackers for %s", self.entity_id) self._unsub_track_device = async_track_state_change( - self.hass, trackers, async_handle_tracker_update) + self.hass, trackers, self._async_handle_tracker_update) - else: - latest = None + self._update_state() + + @callback + def _async_handle_tracker_update(self, entity, old_state, new_state): + """Handle the device tracker state changes.""" + self._update_state() + + @callback + def _update_state(self): + """Update the state.""" + 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 latest: self._parse_source_state(latest) diff --git a/tests/components/person/test_init.py b/tests/components/person/test_init.py index 4cef84746ed..2eacb162f8e 100644 --- a/tests/components/person/test_init.py +++ b/tests/components/person/test_init.py @@ -29,7 +29,7 @@ def storage_setup(hass, hass_storage, hass_admin_user): 'id': '1234', 'name': 'tracked person', 'user_id': hass_admin_user.id, - 'device_trackers': DEVICE_TRACKER + 'device_trackers': [DEVICE_TRACKER] } ] } @@ -189,6 +189,43 @@ async def test_setup_two_trackers(hass, hass_admin_user): assert state.attributes.get(ATTR_USER_ID) == user_id +async def test_ignore_unavailable_states(hass, hass_admin_user): + """Test set up person with two device trackers, one unavailable.""" + user_id = hass_admin_user.id + config = {DOMAIN: { + 'id': '1234', 'name': 'tracked person', 'user_id': user_id, + 'device_trackers': [DEVICE_TRACKER, DEVICE_TRACKER_2]}} + assert await async_setup_component(hass, DOMAIN, config) + + state = hass.states.get('person.tracked_person') + assert state.state == STATE_UNKNOWN + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + await hass.async_block_till_done() + hass.states.async_set(DEVICE_TRACKER, 'home') + await hass.async_block_till_done() + hass.states.async_set(DEVICE_TRACKER, 'unavailable') + await hass.async_block_till_done() + + # Unknown, as only 1 device tracker has a state, but we ignore that one + state = hass.states.get('person.tracked_person') + assert state.state == STATE_UNKNOWN + + hass.states.async_set(DEVICE_TRACKER_2, 'not_home') + await hass.async_block_till_done() + + # Take state of tracker 2 + state = hass.states.get('person.tracked_person') + assert state.state == 'not_home' + + # state 1 is newer but ignored, keep tracker 2 state + hass.states.async_set(DEVICE_TRACKER, 'unknown') + await hass.async_block_till_done() + + state = hass.states.get('person.tracked_person') + assert state.state == 'not_home' + + async def test_restore_home_state(hass, hass_admin_user): """Test that the state is restored for a person on startup.""" user_id = hass_admin_user.id