Skip to content

Commit bdc2406

Browse files
authored
Fix require_movement failing when old state is missing GPS data (pnbruckner#6)
Keep last valid last_seen & GPS data for individual input trackers, even if ultimately not used. Check that last_seen does not go backwards. For GPS-based trackers don't use update if nothing has changed. Ignore warnings for entire initialization period (until EVENT_HOMEASSISTANT_START.)
1 parent 1f02c94 commit bdc2406

File tree

2 files changed

+56
-43
lines changed

2 files changed

+56
-43
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ device_tracker:
2121
2222
### HACS
2323
24-
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/).
24+
See [HACS](https://github.com/custom-components/hacs).
2525
2626
### Manual
2727

custom_components/composite/device_tracker.py

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
_LOGGER = logging.getLogger(__name__)
3030

31-
__version__ = '1.10.0'
31+
__version__ = '1.10.1'
3232

3333
CONF_TIME_AS = 'time_as'
3434
CONF_REQ_MOVEMENT = 'require_movement'
@@ -46,8 +46,9 @@
4646
ATTR_TIME_ZONE = 'time_zone'
4747

4848
WARNED = 'warned'
49+
SEEN = 'seen'
4950
SOURCE_TYPE = ATTR_SOURCE_TYPE
50-
STATE = ATTR_STATE
51+
DATA = 'data'
5152

5253
SOURCE_TYPE_BINARY_SENSOR = BS_DOMAIN
5354
STATE_BINARY_SENSOR_HOME = STATE_ON
@@ -79,8 +80,9 @@ def __init__(self, hass, config, see):
7980
for entity_id in entities:
8081
self._entities[entity_id] = {
8182
WARNED: False,
83+
SEEN: None,
8284
SOURCE_TYPE: None,
83-
STATE: None}
85+
DATA: None}
8486
self._dev_id = config[CONF_NAME]
8587
self._entity_id = ENTITY_ID_FORMAT.format(self._dev_id)
8688
self._time_as = config[CONF_TIME_AS]
@@ -90,15 +92,20 @@ def __init__(self, hass, config, see):
9092
self._req_movement = config[CONF_REQ_MOVEMENT]
9193
self._lock = threading.Lock()
9294
self._prev_seen = None
95+
self._init_complete = False
9396

9497
self._remove = track_state_change(
9598
hass, entities, self._update_info)
9699

97100
for entity_id in entities:
98-
self._update_info(entity_id, None, hass.states.get(entity_id),
99-
init=True)
101+
self._update_info(entity_id, None, hass.states.get(entity_id))
100102

101-
def _bad_entity(self, entity_id, message, init):
103+
def init_complete(event):
104+
self._init_complete = True
105+
106+
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, init_complete)
107+
108+
def _bad_entity(self, entity_id, message):
102109
msg = '{} {}'.format(entity_id, message)
103110
# Has there already been a warning for this entity?
104111
if self._entities[entity_id][WARNED]:
@@ -109,16 +116,19 @@ def _bad_entity(self, entity_id, message, init):
109116
if len(self._entities):
110117
self._remove = track_state_change(
111118
self._hass, self._entities.keys(), self._update_info)
112-
else:
119+
# Don't warn during init.
120+
elif self._init_complete:
113121
_LOGGER.warning(msg)
114-
# Don't count warnings during init.
115-
self._entities[entity_id][WARNED] = not init
122+
self._entities[entity_id][WARNED] = True
123+
else:
124+
_LOGGER.debug(msg)
116125

117-
def _good_entity(self, entity_id, source_type, state):
126+
def _good_entity(self, entity_id, seen, source_type, data):
118127
self._entities[entity_id].update({
119128
WARNED: False,
129+
SEEN: seen,
120130
SOURCE_TYPE: source_type,
121-
STATE: state})
131+
DATA: data})
122132

123133
def _use_non_gps_data(self, state):
124134
if state == STATE_HOME:
@@ -127,7 +137,7 @@ def _use_non_gps_data(self, state):
127137
if any(entity[SOURCE_TYPE] == SOURCE_TYPE_GPS
128138
for entity in entities):
129139
return False
130-
return all(entity[STATE] != STATE_HOME
140+
return all(entity[DATA] != STATE_HOME
131141
for entity in entities
132142
if entity[SOURCE_TYPE] in SOURCE_TYPE_NON_GPS)
133143

@@ -138,7 +148,7 @@ def _dt_attr_from_utc(self, utc, tz):
138148
return dt_util.as_local(utc)
139149
return utc
140150

141-
def _update_info(self, entity_id, old_state, new_state, init=False):
151+
def _update_info(self, entity_id, old_state, new_state):
142152
if new_state is None:
143153
return
144154

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

160-
# Is this newer info than last update?
161-
if self._prev_seen and last_seen <= self._prev_seen:
162-
_LOGGER.debug(
163-
'For {} skipping update from {}: '
164-
'last_seen not newer than previous update ({} <= {})'
165-
.format(self._entity_id, entity_id, last_seen,
166-
self._prev_seen))
170+
old_last_seen = self._entities[entity_id][SEEN]
171+
if old_last_seen and last_seen < old_last_seen:
172+
self._bad_entity(entity_id, 'last_seen went backwards')
167173
return
168174

169175
# Try to get GPS and battery data.
@@ -191,30 +197,29 @@ def _update_info(self, entity_id, old_state, new_state, init=False):
191197
if source_type == SOURCE_TYPE_GPS:
192198
# GPS coordinates and accuracy are required.
193199
if gps is None:
194-
self._bad_entity(entity_id,
195-
'missing gps attributes', init)
200+
self._bad_entity(entity_id, 'missing gps attributes')
196201
return
197202
if gps_accuracy is None:
198203
self._bad_entity(entity_id,
199-
'missing gps_accuracy attribute', init)
204+
'missing gps_accuracy attribute')
200205
return
201-
if self._req_movement and old_state is not None:
202-
try:
203-
old_lat = old_state.attributes[ATTR_LATITUDE]
204-
old_lon = old_state.attributes[ATTR_LONGITUDE]
205-
old_acc = old_state.attributes[ATTR_GPS_ACCURACY]
206-
except KeyError:
207-
self._bad_entity(entity_id,
208-
'old_state missing gps data', init)
206+
207+
new_data = gps, gps_accuracy
208+
old_data = self._entities[entity_id][DATA]
209+
if old_data:
210+
if last_seen == old_last_seen and new_data == old_data:
209211
return
210-
if (distance(gps[0], gps[1], old_lat, old_lon) <=
212+
old_gps, old_acc = old_data
213+
self._good_entity(entity_id, last_seen, source_type, new_data)
214+
215+
if (self._req_movement and old_data and
216+
distance(gps[0], gps[1], old_gps[0], old_gps[1]) <=
211217
gps_accuracy + old_acc):
212-
_LOGGER.debug(
213-
'For {} skipping update from {}: '
214-
'not enough movement'
215-
.format(self._entity_id, entity_id))
216-
return
217-
self._good_entity(entity_id, SOURCE_TYPE_GPS, state)
218+
_LOGGER.debug(
219+
'For {} skipping update from {}: '
220+
'not enough movement'
221+
.format(self._entity_id, entity_id))
222+
return
218223

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

228-
self._good_entity(
229-
entity_id, source_type, state)
233+
self._good_entity(entity_id, last_seen, source_type, state)
234+
230235
if not self._use_non_gps_data(state):
231236
return
232237

@@ -274,8 +279,16 @@ def _update_info(self, entity_id, old_state, new_state, init=False):
274279
else:
275280
self._bad_entity(
276281
entity_id,
277-
'unsupported source_type: {}'.format(source_type),
278-
init)
282+
'unsupported source_type: {}'.format(source_type))
283+
return
284+
285+
# Is this newer info than last update?
286+
if self._prev_seen and last_seen <= self._prev_seen:
287+
_LOGGER.debug(
288+
'For {} skipping update from {}: '
289+
'last_seen not newer than previous update ({} <= {})'
290+
.format(self._entity_id, entity_id, last_seen,
291+
self._prev_seen))
279292
return
280293

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

0 commit comments

Comments
 (0)