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!')
Exemple #3
0
    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)
Exemple #5
0
    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")
Exemple #6
0
    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
Exemple #10
0
    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'))
Exemple #11
0
    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