Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ device_tracker:

### HACS

See [HACS](https://github.com/custom-components/hacs), especially the **Add custom repositories** section on [this page](https://custom-components.github.io/hacs/usage/settings/).
See [HACS](https://github.com/custom-components/hacs).

### Manual

Expand Down
97 changes: 55 additions & 42 deletions custom_components/composite/device_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

_LOGGER = logging.getLogger(__name__)

__version__ = '1.10.0'
__version__ = '1.10.1'

CONF_TIME_AS = 'time_as'
CONF_REQ_MOVEMENT = 'require_movement'
Expand All @@ -46,8 +46,9 @@
ATTR_TIME_ZONE = 'time_zone'

WARNED = 'warned'
SEEN = 'seen'
SOURCE_TYPE = ATTR_SOURCE_TYPE
STATE = ATTR_STATE
DATA = 'data'

SOURCE_TYPE_BINARY_SENSOR = BS_DOMAIN
STATE_BINARY_SENSOR_HOME = STATE_ON
Expand Down Expand Up @@ -79,8 +80,9 @@ def __init__(self, hass, config, see):
for entity_id in entities:
self._entities[entity_id] = {
WARNED: False,
SEEN: None,
SOURCE_TYPE: None,
STATE: None}
DATA: None}
self._dev_id = config[CONF_NAME]
self._entity_id = ENTITY_ID_FORMAT.format(self._dev_id)
self._time_as = config[CONF_TIME_AS]
Expand All @@ -90,15 +92,20 @@ def __init__(self, hass, config, see):
self._req_movement = config[CONF_REQ_MOVEMENT]
self._lock = threading.Lock()
self._prev_seen = None
self._init_complete = False

self._remove = track_state_change(
hass, entities, self._update_info)

for entity_id in entities:
self._update_info(entity_id, None, hass.states.get(entity_id),
init=True)
self._update_info(entity_id, None, hass.states.get(entity_id))

def _bad_entity(self, entity_id, message, init):
def init_complete(event):
self._init_complete = True

hass.bus.listen_once(EVENT_HOMEASSISTANT_START, init_complete)

def _bad_entity(self, entity_id, message):
msg = '{} {}'.format(entity_id, message)
# Has there already been a warning for this entity?
if self._entities[entity_id][WARNED]:
Expand All @@ -109,16 +116,19 @@ def _bad_entity(self, entity_id, message, init):
if len(self._entities):
self._remove = track_state_change(
self._hass, self._entities.keys(), self._update_info)
else:
# Don't warn during init.
elif self._init_complete:
_LOGGER.warning(msg)
# Don't count warnings during init.
self._entities[entity_id][WARNED] = not init
self._entities[entity_id][WARNED] = True
else:
_LOGGER.debug(msg)

def _good_entity(self, entity_id, source_type, state):
def _good_entity(self, entity_id, seen, source_type, data):
self._entities[entity_id].update({
WARNED: False,
SEEN: seen,
SOURCE_TYPE: source_type,
STATE: state})
DATA: data})

def _use_non_gps_data(self, state):
if state == STATE_HOME:
Expand All @@ -127,7 +137,7 @@ def _use_non_gps_data(self, state):
if any(entity[SOURCE_TYPE] == SOURCE_TYPE_GPS
for entity in entities):
return False
return all(entity[STATE] != STATE_HOME
return all(entity[DATA] != STATE_HOME
for entity in entities
if entity[SOURCE_TYPE] in SOURCE_TYPE_NON_GPS)

Expand All @@ -138,7 +148,7 @@ def _dt_attr_from_utc(self, utc, tz):
return dt_util.as_local(utc)
return utc

def _update_info(self, entity_id, old_state, new_state, init=False):
def _update_info(self, entity_id, old_state, new_state):
if new_state is None:
return

Expand All @@ -157,13 +167,9 @@ def _update_info(self, entity_id, old_state, new_state, init=False):
except (TypeError, ValueError):
last_seen = new_state.last_updated

# Is this newer info than last update?
if self._prev_seen and last_seen <= self._prev_seen:
_LOGGER.debug(
'For {} skipping update from {}: '
'last_seen not newer than previous update ({} <= {})'
.format(self._entity_id, entity_id, last_seen,
self._prev_seen))
old_last_seen = self._entities[entity_id][SEEN]
if old_last_seen and last_seen < old_last_seen:
self._bad_entity(entity_id, 'last_seen went backwards')
return

# Try to get GPS and battery data.
Expand Down Expand Up @@ -191,30 +197,29 @@ def _update_info(self, entity_id, old_state, new_state, init=False):
if source_type == SOURCE_TYPE_GPS:
# GPS coordinates and accuracy are required.
if gps is None:
self._bad_entity(entity_id,
'missing gps attributes', init)
self._bad_entity(entity_id, 'missing gps attributes')
return
if gps_accuracy is None:
self._bad_entity(entity_id,
'missing gps_accuracy attribute', init)
'missing gps_accuracy attribute')
return
if self._req_movement and old_state is not None:
try:
old_lat = old_state.attributes[ATTR_LATITUDE]
old_lon = old_state.attributes[ATTR_LONGITUDE]
old_acc = old_state.attributes[ATTR_GPS_ACCURACY]
except KeyError:
self._bad_entity(entity_id,
'old_state missing gps data', init)

new_data = gps, gps_accuracy
old_data = self._entities[entity_id][DATA]
if old_data:
if last_seen == old_last_seen and new_data == old_data:
return
if (distance(gps[0], gps[1], old_lat, old_lon) <=
old_gps, old_acc = old_data
self._good_entity(entity_id, last_seen, source_type, new_data)

if (self._req_movement and old_data and
distance(gps[0], gps[1], old_gps[0], old_gps[1]) <=
gps_accuracy + old_acc):
_LOGGER.debug(
'For {} skipping update from {}: '
'not enough movement'
.format(self._entity_id, entity_id))
return
self._good_entity(entity_id, SOURCE_TYPE_GPS, state)
_LOGGER.debug(
'For {} skipping update from {}: '
'not enough movement'
.format(self._entity_id, entity_id))
return

elif source_type in SOURCE_TYPE_NON_GPS:
# Convert 'on'/'off' state of binary_sensor
Expand All @@ -225,8 +230,8 @@ def _update_info(self, entity_id, old_state, new_state, init=False):
else:
state = STATE_NOT_HOME

self._good_entity(
entity_id, source_type, state)
self._good_entity(entity_id, last_seen, source_type, state)

if not self._use_non_gps_data(state):
return

Expand Down Expand Up @@ -274,8 +279,16 @@ def _update_info(self, entity_id, old_state, new_state, init=False):
else:
self._bad_entity(
entity_id,
'unsupported source_type: {}'.format(source_type),
init)
'unsupported source_type: {}'.format(source_type))
return

# Is this newer info than last update?
if self._prev_seen and last_seen <= self._prev_seen:
_LOGGER.debug(
'For {} skipping update from {}: '
'last_seen not newer than previous update ({} <= {})'
.format(self._entity_id, entity_id, last_seen,
self._prev_seen))
return

_LOGGER.debug('Updating %s from %s', self._entity_id, entity_id)
Expand Down