def __init__(self, hass, config, see): self._hass = hass self._see = see entities = config[CONF_ENTITY_ID] self._entities = {} for entity_id in entities: self._entities[entity_id] = { WARNED: False, SOURCE_TYPE: None, STATE: None } self._dev_id = config[CONF_NAME] self._entity_id = ENTITY_ID_FORMAT.format(self._dev_id) self._time_as = config[CONF_TIME_AS] if self._time_as in [TZ_DEVICE_UTC, TZ_DEVICE_LOCAL]: from timezonefinderL import TimezoneFinder self._tf = TimezoneFinder() self._lock = threading.Lock() self._prev_seen = None 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)
def update_icloud(now): """Authenticate against iCloud and scan for devices.""" try: keep_alive(None) # Loop through every device registered with the iCloud account for device in api.devices: status = device.status() dev_id = slugify(status['name'].replace(' ', '', 99)) # An entity will not be created by see() when track=false in # 'known_devices.yaml', but we need to see() it at least once entity = hass.states.get(ENTITY_ID_FORMAT.format(dev_id)) if entity is None and dev_id in seen_devices: continue seen_devices[dev_id] = True location = device.location() # If the device has a location add it. If not do nothing if location: see(dev_id=dev_id, host_name=status['name'], gps=(location['latitude'], location['longitude']), battery=status['batteryLevel'] * 100, gps_accuracy=location['horizontalAccuracy']) except PyiCloudNoDevicesException: _LOGGER.info('No iCloud Devices found!')
def update_icloud(now): """Authenticate against iCloud and scan for devices.""" try: keep_alive(None) # Loop through every device registered with the iCloud account for device in api.devices: status = device.status() dev_id = slugify(status["name"].replace(" ", "", 99)) # An entity will not be created by see() when track=false in # 'known_devices.yaml', but we need to see() it at least once entity = hass.states.get(ENTITY_ID_FORMAT.format(dev_id)) if entity is None and dev_id in seen_devices: continue seen_devices[dev_id] = True location = device.location() # If the device has a location add it. If not do nothing if location: see( dev_id=dev_id, host_name=status["name"], gps=(location["latitude"], location["longitude"]), battery=status["batteryLevel"] * 100, gps_accuracy=location["horizontalAccuracy"], ) except PyiCloudNoDevicesException: _LOGGER.info("No iCloud Devices found!")
def _update_info(self, now=None): for dev in self.service.devices: dev_id = "sherlock_{0}".format(dev.sherlock_id) entity_id = ENTITY_ID_FORMAT.format(dev_id) # Get previous values. entity = self.hass.states.get(entity_id) prev_lat = entity.attributes.get("latitude") if entity else None prev_lon = entity.attributes.get("longitude") if entity else None prev_last_seen = (entity.attributes.get("last_seen") if entity else None) prev_state = entity.attributes.get("state") if entity else None # Default to prev lat/lon/last_seen values if they are not # currently determined. fullname = "{0} {1}".format(self.service.user.get("name"), self.service.user.get("surname")) lat = dev.location.latitude if dev.location else prev_lat lon = dev.location.longitude if dev.location else prev_lon last_seen = (dev.location.last_update if dev.location else prev_last_seen) attrs = { ATTR_BATTERY_LEVEL: dev.battery_level, ATTR_BIKE_FRAME_NUMBER: dev.bike_frame_number, ATTR_BIKE_SERIAL_NUMBER: dev.bike_serial_number, ATTR_EMAIL: self.service.user.get("email"), ATTR_FIRMWARE_VERSION: dev.firmware_version, ATTR_FRIENDLY_NAME: dev.bike_model, ATTR_FULL_NAME: fullname, ATTR_IMEI: dev.imei, ATTR_LAST_SEEN: last_seen, ATTR_NAME: dev.bike_model, ATTR_STATE: dev.state, ATTR_USER_ID: self.service.user.get("user_id"), } self.see( dev_id=dev_id, gps=(lat, lon), picture=dev.bike_picture_url, source_type=SOURCE_TYPE_GPS, # gps_accuracy=dev.accuracy, attributes=attrs ) # Fire an event in case the state updated to ALARMED. if dev.state == STATE_ALARM and prev_state != STATE_ALARM: _LOGGER.info("Sherlock alarm fired") event_data = { 'latitude': lat, 'longitude': lon, 'sherlock_id': dev.sherlock_id, 'entity_id': entity_id } self.hass.bus.fire(EVENT_SHERLOCK_ALARM, event_data)
def update_device(self, devicename): """Update the device_tracker entity.""" from pyicloud.exceptions import PyiCloudNoDevicesException # An entity will not be created by see() when track=false in # 'known_devices.yaml', but we need to see() it at least once entity = self.hass.states.get(ENTITY_ID_FORMAT.format(devicename)) if entity is None and devicename in self.seen_devices: return attrs = {} kwargs = {} if self.api is None: return if self.filter_devices not in devicename: return try: for device in self.api.devices: if str(device) != str(self.devices[devicename]): continue status = device.status(DEVICESTATUSSET) dev_id = status['name'].replace(' ', '', 99) dev_id = slugify(dev_id) attrs[ATTR_DEVICESTATUS] = DEVICESTATUSCODES.get( status['deviceStatus'], 'error') attrs[ATTR_LOWPOWERMODE] = status['lowPowerMode'] attrs[ATTR_BATTERYSTATUS] = status['batteryStatus'] attrs[ATTR_ACCOUNTNAME] = self.accountname status = device.status(DEVICESTATUSSET) battery = status.get('batteryLevel', 0) * 100 location = status['location'] if location: self.determine_interval( devicename, location['latitude'], location['longitude'], battery) interval = self._intervals.get(devicename, 1) attrs[ATTR_INTERVAL] = interval accuracy = location['horizontalAccuracy'] kwargs['dev_id'] = dev_id kwargs['host_name'] = status['name'] kwargs['gps'] = (location['latitude'], location['longitude']) kwargs['battery'] = battery kwargs['gps_accuracy'] = accuracy kwargs[ATTR_ATTRIBUTES] = attrs self.see(**kwargs) self.seen_devices[devicename] = True except PyiCloudNoDevicesException: _LOGGER.error("No iCloud Devices found")
def update_device(self, devicename): """Update the device_tracker entity.""" from pyicloud.exceptions import PyiCloudNoDevicesException # An entity will not be created by see() when track=false in # 'known_devices.yaml', but we need to see() it at least once entity = self.hass.states.get(ENTITY_ID_FORMAT.format(devicename)) if entity is None and devicename in self.seen_devices: return attrs = {} kwargs = {} if self.api is None: return try: for device in self.api.devices: if str(device) != str(self.devices[devicename]): continue status = device.status(DEVICESTATUSSET) _LOGGER.debug('Device Status is %s', status) dev_id = status['name'].replace(' ', '', 99) dev_id = slugify(dev_id) attrs[ATTR_DEVICESTATUS] = DEVICESTATUSCODES.get( status['deviceStatus'], 'error') attrs[ATTR_LOWPOWERMODE] = status['lowPowerMode'] attrs[ATTR_BATTERYSTATUS] = status['batteryStatus'] attrs[ATTR_ACCOUNTNAME] = self.accountname status = device.status(DEVICESTATUSSET) battery = status.get('batteryLevel', 0) * 100 location = status['location'] if location and location['horizontalAccuracy']: horizontal_accuracy = int(location['horizontalAccuracy']) if horizontal_accuracy < self._gps_accuracy_threshold: self.determine_interval( devicename, location['latitude'], location['longitude'], battery) interval = self._intervals.get(devicename, 1) attrs[ATTR_INTERVAL] = interval accuracy = location['horizontalAccuracy'] kwargs['dev_id'] = dev_id kwargs['host_name'] = status['name'] kwargs['gps'] = (location['latitude'], location['longitude']) kwargs['battery'] = battery kwargs['gps_accuracy'] = accuracy kwargs[ATTR_ATTRIBUTES] = attrs self.see(**kwargs) self.seen_devices[devicename] = True except PyiCloudNoDevicesException: _LOGGER.error("No iCloud Devices found")
def update_device(self, devicename): """Update the device_tracker entity.""" from pyicloud.exceptions import PyiCloudNoDevicesException # An entity will not be created by see() when track=false in # 'known_devices.yaml', but we need to see() it at least once entity = self.hass.states.get(ENTITY_ID_FORMAT.format(devicename)) if entity is None and devicename in self.seen_devices: return attrs = {} kwargs = {} if self.api is None: return try: for device in self.api.devices: if str(device) != str(self.devices[devicename]): continue status = device.status(DEVICESTATUSSET) dev_id = status["name"].replace(" ", "", 99) dev_id = slugify(dev_id) attrs[ATTR_DEVICESTATUS] = DEVICESTATUSCODES.get(status["deviceStatus"], "error") attrs[ATTR_LOWPOWERMODE] = status["lowPowerMode"] attrs[ATTR_BATTERYSTATUS] = status["batteryStatus"] attrs[ATTR_ACCOUNTNAME] = self.accountname status = device.status(DEVICESTATUSSET) battery = status.get("batteryLevel", 0) * 100 location = status["location"] if location: self.determine_interval(devicename, location["latitude"], location["longitude"], battery) interval = self._intervals.get(devicename, 1) attrs[ATTR_INTERVAL] = interval accuracy = location["horizontalAccuracy"] kwargs["dev_id"] = dev_id kwargs["host_name"] = status["name"] kwargs["gps"] = (location["latitude"], location["longitude"]) kwargs["battery"] = battery kwargs["gps_accuracy"] = accuracy kwargs[ATTR_ATTRIBUTES] = attrs self.see(**kwargs) self.seen_devices[devicename] = True except PyiCloudNoDevicesException: _LOGGER.error("No iCloud Devices found!")
def __init__(self, hass, config, see): self._hass = hass self._see = see entities = config[CONF_ENTITY_ID] self._entities = {} for entity_id in entities: self._entities[entity_id] = { WARNED: False, SOURCE_TYPE: None, STATE: None } self._dev_id = config[CONF_NAME] self._entity_id = ENTITY_ID_FORMAT.format(self._dev_id) self._lock = threading.Lock() self._prev_seen = None 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)
def _update_member(self, m): f = m['firstName'] l = m['lastName'] #_LOGGER.debug('Checking "{}, {}"'.format(f, l)) m_name = ('_'.join([f, l]) if f and l else f or l).replace('-', '_') dev_id = util.slugify(self._prefix + m_name) prev_update, reported = self._dev_data.get(dev_id, (None, False)) loc = m.get('location') last_update = None if not loc else utc_from_ts(loc['timestamp']) if self._max_update_wait: update = last_update or prev_update or self._started overdue = util.dt.utcnow() - update > self._max_update_wait if overdue and not reported: self._hass.bus.fire( 'device_tracker.life360_update_overdue', {'entity_id': ENTITY_ID_FORMAT.format(dev_id)}) reported = True elif not overdue and reported: self._hass.bus.fire( 'device_tracker.life360_update_restored', { 'entity_id': ENTITY_ID_FORMAT.format(dev_id), 'wait': str(last_update - (prev_update or self._started)).split('.')[0] }) reported = False if not loc: err_msg = m['issues']['title'] if err_msg: if m['issues']['dialog']: err_msg += ': ' + m['issues']['dialog'] else: err_msg = 'Location information missing' _LOGGER.error('{}: {}'.format(dev_id, err_msg)) elif prev_update is None or last_update > prev_update: msg = 'Updating {}'.format(dev_id) if prev_update is not None: msg += '; Time since last update: {}'.format(last_update - prev_update) _LOGGER.debug(msg) attrs = { ATTR_LAST_UPDATE: dt_attr_from_utc(last_update), ATTR_AT_LOC_SINCE: dt_attr_from_ts(loc['since']), ATTR_MOVING: bool_attr_from_int(loc['inTransit']), ATTR_CHARGING: bool_attr_from_int(loc['charge']), ATTR_WIFI_ON: bool_attr_from_int(loc['wifiState']), ATTR_DRIVING: bool_attr_from_int(loc['isDriving']) } lat = float(loc['latitude']) lon = float(loc['longitude']) gps_accuracy = round(float(loc['accuracy'])) # Does user want location name to be shown as state? loc_name = loc[ 'name'] if ATTR_PLACES in self._show_as_state else None # Make sure Home is always seen as exactly as home, # which is the special device_tracker state for home. if loc_name is not None and loc_name.lower() == 'home': loc_name = 'home' # If we don't have a location name yet and user wants driving or moving # to be shown as state, and current location is not in a HA zone, # then update location name accordingly. if not loc_name and not active_zone(self._hass, lat, lon, gps_accuracy): if ATTR_DRIVING in self._show_as_state and attrs[ ATTR_DRIVING] is True: loc_name = ATTR_DRIVING.capitalize() elif ATTR_MOVING in self._show_as_state and attrs[ ATTR_MOVING] is True: loc_name = ATTR_MOVING.capitalize() self._see(dev_id=dev_id, location_name=loc_name, gps=(lat, lon), gps_accuracy=gps_accuracy, battery=round(float(loc['battery'])), attributes=attrs) self._dev_data[dev_id] = last_update or prev_update, reported
def _update_member(self, m, name): name = name.replace(',', '_').replace('-', '_') dev_id = slugify(self._prefix + name) prev_seen, reported = self._dev_data.get(dev_id, (None, False)) loc = m.get('location') try: last_seen = utc_from_ts(loc.get('timestamp')) except AttributeError: last_seen = None if self._max_update_wait: update = last_seen or prev_seen or self._started overdue = dt_util.utcnow() - update > self._max_update_wait if overdue and not reported: self._hass.bus.fire( 'life360_update_overdue', {'entity_id': DT_ENTITY_ID_FORMAT.format(dev_id)}) reported = True elif not overdue and reported: self._hass.bus.fire( 'life360_update_restored', { 'entity_id': DT_ENTITY_ID_FORMAT.format(dev_id), 'wait': str(last_seen - (prev_seen or self._started)).split('.')[0] }) reported = False self._dev_data[dev_id] = last_seen or prev_seen, reported if not loc: err_msg = m['issues']['title'] if err_msg: if m['issues']['dialog']: err_msg += ': ' + m['issues']['dialog'] else: err_msg = 'Location information missing' self._err(dev_id, err_msg) return if last_seen and (not prev_seen or last_seen > prev_seen): lat = loc.get('latitude') lon = loc.get('longitude') gps_accuracy = loc.get('accuracy') try: lat = float(lat) lon = float(lon) # Life360 reports accuracy in feet, but Device Tracker expects # gps_accuracy in meters. gps_accuracy = round( convert(float(gps_accuracy), LENGTH_FEET, LENGTH_METERS)) except (TypeError, ValueError): self._err( dev_id, 'GPS data invalid: {}, {}, {}'.format( lat, lon, gps_accuracy)) return self._ok(dev_id) msg = 'Updating {}'.format(dev_id) if prev_seen: msg += '; Time since last update: {}'.format(last_seen - prev_seen) _LOGGER.debug(msg) if (self._max_gps_accuracy is not None and gps_accuracy > self._max_gps_accuracy): _LOGGER.info('{}: Ignoring update because expected GPS ' 'accuracy {} is not met: {}'.format( dev_id, gps_accuracy, self._max_gps_accuracy)) return place_name = loc.get('name') or None # Does user want location name to be shown as state? if SHOW_PLACES in self._show_as_state: loc_name = place_name # Make sure Home Place is always seen exactly as home, # which is the special device_tracker state for home. if loc_name and loc_name.lower() == self._home_place: loc_name = STATE_HOME else: loc_name = None # If a place name is given, then address will just be a copy of # it, so don't bother with address. Otherwise, piece address # lines together, depending on which are present. if place_name: address = None else: address1 = loc.get('address1') or None address2 = loc.get('address2') or None if address1 and address2: address = ', '.join([address1, address2]) else: address = address1 or address2 raw_speed = loc.get('speed') try: speed = float(raw_speed) * SPEED_FACTOR_MPH if self._hass.config.units.is_metric: speed = convert(speed, LENGTH_MILES, LENGTH_KILOMETERS) speed = max(0, round(speed)) except (TypeError, ValueError): speed = STATE_UNKNOWN driving = bool_attr_from_int(loc.get('isDriving')) if (driving in (STATE_UNKNOWN, False) and self._driving_speed is not None and speed != STATE_UNKNOWN): driving = speed >= self._driving_speed moving = bool_attr_from_int(loc.get('inTransit')) if self._time_as in [TZ_DEVICE_UTC, TZ_DEVICE_LOCAL]: # timezone_at will return a string or None. tzname = self._tf.timezone_at(lng=lon, lat=lat) # get_time_zone will return a tzinfo or None. tz = dt_util.get_time_zone(tzname) attrs = {ATTR_TIME_ZONE: tzname or STATE_UNKNOWN} else: tz = None attrs = {} attrs.update({ ATTR_ADDRESS: address, ATTR_AT_LOC_SINCE: self._dt_attr_from_ts(loc.get('since'), tz), ATTR_BATTERY_CHARGING: bool_attr_from_int(loc.get('charge')), ATTR_DRIVING: driving, ATTR_LAST_SEEN: self._dt_attr_from_utc(last_seen, tz), ATTR_MOVING: moving, ATTR_RAW_SPEED: raw_speed, ATTR_SPEED: speed, ATTR_WIFI_ON: bool_attr_from_int(loc.get('wifiState')), }) # If we don't have a location name yet and user wants driving or moving # to be shown as state, and current location is not in a HA zone, # then update location name accordingly. if not loc_name and not active_zone(self._hass, lat, lon, gps_accuracy): if SHOW_DRIVING in self._show_as_state and driving is True: loc_name = SHOW_DRIVING.capitalize() elif SHOW_MOVING in self._show_as_state and moving is True: loc_name = SHOW_MOVING.capitalize() try: battery = int(float(loc.get('battery'))) except (TypeError, ValueError): battery = None self._see(dev_id=dev_id, location_name=loc_name, gps=(lat, lon), gps_accuracy=gps_accuracy, battery=battery, attributes=attrs, picture=m.get('avatar'))
def _update_member(self, m, name): name = name.replace(',', '_').replace('-', '_') dev_id = util.slugify(self._prefix + name) prev_seen, reported = self._dev_data.get(dev_id, (None, False)) loc = m.get('location') try: last_seen = utc_from_ts(loc.get('timestamp')) except AttributeError: last_seen = None if self._max_update_wait: update = last_seen or prev_seen or self._started overdue = util.dt.utcnow() - update > self._max_update_wait if overdue and not reported: self._hass.bus.fire( 'device_tracker.life360_update_overdue', {'entity_id': ENTITY_ID_FORMAT.format(dev_id)}) reported = True elif not overdue and reported: self._hass.bus.fire( 'device_tracker.life360_update_restored', { 'entity_id': ENTITY_ID_FORMAT.format(dev_id), 'wait': str(last_seen - (prev_seen or self._started)).split('.')[0] }) reported = False self._dev_data[dev_id] = last_seen or prev_seen, reported if not loc: err_msg = m['issues']['title'] if err_msg: if m['issues']['dialog']: err_msg += ': ' + m['issues']['dialog'] else: err_msg = 'Location information missing' self._err(dev_id, err_msg) return if last_seen and (not prev_seen or last_seen > prev_seen): lat = loc.get('latitude') lon = loc.get('longitude') gps_accuracy = loc.get('accuracy') try: lat = float(lat) lon = float(lon) # Life360 reports accuracy in feet, but Device Tracker expects # gps_accuracy in meters. gps_accuracy = round(float(gps_accuracy) * 0.3048) except (TypeError, ValueError): self._err( dev_id, 'GPS data invalid: {}, {}, {}'.format( lat, lon, gps_accuracy)) return self._ok(dev_id) msg = 'Updating {}'.format(dev_id) if prev_seen: msg += '; Time since last update: {}'.format(last_seen - prev_seen) _LOGGER.debug(msg) if (self._max_gps_accuracy is not None and gps_accuracy > self._max_gps_accuracy): _LOGGER.info('{}: Ignoring update because expected GPS ' 'accuracy {} is not met: {}'.format( dev_id, gps_accuracy, self._max_gps_accuracy)) return place_name = loc.get('name') or None # Does user want location name to be shown as state? if ATTR_PLACES in self._show_as_state: loc_name = place_name # Make sure Home is always seen as exactly as home, # which is the special device_tracker state for home. if loc_name and loc_name.lower() == 'home': loc_name = 'home' else: loc_name = None # If a place name is given, then address will just be a copy of # it, so don't bother with address. Otherwise, piece address # lines together, depending on which are present. if place_name: address = None else: address1 = loc.get('address1') or None address2 = loc.get('address2') or None if address1 and address2: address = ', '.join([address1, address2]) else: address = address1 or address2 attrs = { ATTR_LAST_SEEN: last_seen, ATTR_AT_LOC_SINCE: utc_attr_from_ts(loc.get('since')), ATTR_MOVING: bool_attr_from_int(loc.get('inTransit')), ATTR_CHARGING: bool_attr_from_int(loc.get('charge')), ATTR_WIFI_ON: bool_attr_from_int(loc.get('wifiState')), ATTR_DRIVING: bool_attr_from_int(loc.get('isDriving')), ATTR_ADDRESS: address } # If we don't have a location name yet and user wants driving or moving # to be shown as state, and current location is not in a HA zone, # then update location name accordingly. if not loc_name and not active_zone(self._hass, lat, lon, gps_accuracy): if ATTR_DRIVING in self._show_as_state and attrs[ ATTR_DRIVING] is True: loc_name = ATTR_DRIVING.capitalize() elif ATTR_MOVING in self._show_as_state and attrs[ ATTR_MOVING] is True: loc_name = ATTR_MOVING.capitalize() try: battery = float(loc.get('battery')) except (TypeError, ValueError): battery = None self._see(dev_id=dev_id, location_name=loc_name, gps=(lat, lon), gps_accuracy=gps_accuracy, battery=battery, attributes=attrs, picture=m.get('avatar'))
def _update_info(self, entity_id, old_state, new_state, init=False): if new_state is None: return with self._lock: composite_entity_id = ENTITY_ID_FORMAT.format(self._dev_id) # Get time device was last seen, which is the entity's last_seen # attribute, or if that doesn't exist, then last_updated from the # new state object. Make sure last_seen is timezone aware in UTC. # Note that util.dt.as_utc assumes naive datetime is in local # timezone. last_seen = new_state.attributes.get(ATTR_LAST_SEEN) if isinstance(last_seen, datetime): last_seen = util.dt.as_utc(last_seen) else: try: last_seen = util.dt.utc_from_timestamp(float(last_seen)) except (TypeError, ValueError): last_seen = new_state.last_updated # Is this newer info than last update? if self._prev_seen and self._prev_seen >= last_seen: _LOGGER.debug( 'For {} skipping update from {}: ' 'last_seen older than previous update ({} < {})' .format(composite_entity_id, entity_id, last_seen, self._prev_seen)) return # Try to get GPS and battery data. try: gps = (new_state.attributes[ATTR_LATITUDE], new_state.attributes[ATTR_LONGITUDE]) except KeyError: gps = None gps_accuracy = new_state.attributes.get(ATTR_GPS_ACCURACY) battery = new_state.attributes.get(ATTR_BATTERY) # Don't use location_name unless we have to. location_name = None # What type of tracker is this? if new_state.domain == BS_DOMAIN: source_type = SOURCE_TYPE_BINARY_SENSOR else: source_type = new_state.attributes.get(ATTR_SOURCE_TYPE) state = new_state.state 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) return if gps_accuracy is None: self._bad_entity(entity_id, 'missing gps_accuracy attribute', init) return self._good_entity(entity_id, SOURCE_TYPE_GPS, state) elif source_type in SOURCE_TYPE_NON_GPS: # Convert 'on'/'off' state of binary_sensor # to 'home'/'not_home'. if source_type == SOURCE_TYPE_BINARY_SENSOR: if state == STATE_BINARY_SENSOR_HOME: state = STATE_HOME else: state = STATE_NOT_HOME self._good_entity( entity_id, source_type, state) if not self._use_non_gps_data(state): return # Don't use new GPS data if it's not complete. if gps is None or gps_accuracy is None: gps = gps_accuracy = None # Get current GPS data, if any, and determine if it is in # 'zone.home'. cur_state = self._hass.states.get(composite_entity_id) try: cur_lat = cur_state.attributes[ATTR_LATITUDE] cur_lon = cur_state.attributes[ATTR_LONGITUDE] cur_acc = cur_state.attributes[ATTR_GPS_ACCURACY] cur_gps_is_home = ( active_zone(self._hass, cur_lat, cur_lon, cur_acc) .entity_id == 'zone.home') except (AttributeError, KeyError): cur_gps_is_home = False # It's important, for this composite tracker, to avoid the # component level code's "stale processing." This can be done # one of two ways: 1) provide GPS data w/ source_type of gps, # or 2) provide a location_name (that will be used as the new # state.) # If router entity's state is 'home' and current GPS data from # composite entity is available and is in 'zone.home', # use it and make source_type gps. if state == STATE_HOME and cur_gps_is_home: gps = cur_lat, cur_lon gps_accuracy = cur_acc source_type = SOURCE_TYPE_GPS # Otherwise, if new GPS data is valid (which is unlikely if # new state is not 'home'), # use it and make source_type gps. elif gps: source_type = SOURCE_TYPE_GPS # Otherwise, don't use any GPS data, but set location_name to # new state. else: location_name = state else: self._bad_entity( entity_id, 'unsupported source_type: {}'.format(source_type), init) return attrs = { ATTR_ENTITY_ID: tuple( entity_id for entity_id, entity in self._entities.items() if entity[ATTR_SOURCE_TYPE] is not None), ATTR_LAST_ENTITY_ID: entity_id, ATTR_LAST_SEEN: last_seen.replace(microsecond=0)} self._see(dev_id=self._dev_id, location_name=location_name, gps=gps, gps_accuracy=gps_accuracy, battery=battery, attributes=attrs, source_type=source_type) self._prev_seen = last_seen