Life 360 support

@editter Instead of making it publish to two places, can you look at the script juan11perez posted and have it use similar code to publish to mqtt in owntracks format so that we can just add the own tracks device tracker and automatically get the users added. It will also let people use recorder if they want for location history out of the box. Right now I am running your add-on plus that custom sensor to accomplish both

I am going to try and add the life360 component into the main HA code which would give you everything you need natively. Plus it would make it so you don’t need to also add the owntracks line to your configuration.

When I wrote this addon it was literally the first thing I did after getting started with HA and since then I have learned a lot about how the code works inside of HA. The addon is a little unnecessary when there are so many things already built in that can handle it :slight_smile:

@editter, I do like the owntracks_http support tho which takes one requirement (mqtt server) away. I guess in a perfect world I would have a device_tracker custom component like pnbruckner’s that also in the background would publish to mqtt in owntracks format (like the one juan posted) so I have history data using owntracks recorder.

You wouldn’t need to have owntracks manage the history if it is a component because the life360 component would have it’s own history. Unless you mean track it in owntracks website or something?

Yea it’s a location mapping server they wrote.

https://github.com/owntracks/recorder. They have a docker image where it and mqtt run together so it’s set out of the box to grab and map the data.

Ahh so it’s completely external to Home Assistants recorder? Well if that is the case I would recommend setting up an automation that takes data from Life360 and pushes it to that. It seems wrong to have this component change it’s data structure to match a, more or less, unrelated component. If you need help with that please let me know :slight_smile:

Yea, it just subscribes to the owntracks mqtt topic and uses the standard owntracks format that the owntracks app uses that home assistant already supports.I had previously considered taking any location events (say from any device tracker) and using a payload template to grab the data and format it to owntracks format and the mqtt publish component to push it to my mqtt server. I just hadn’t even gotten to figuring out if I could get any gps as it arrived to trigger automations or even to working on a template to format it. It was just an idea I had since owntracks seems to be the least consistent in location updates for me. Finding the python script above that already did the work let me work on other things instead.

Hi @shaddow, you didn’t tag me so I didn’t know you mentioned me until I happened to go back and search this topic. :slight_smile: Which, BTW, I did because I have an update for the custom Life360 device tracker platform I made. Maybe I should be posting this elsewhere, but oh well, here goes…

The full code is below, but since I posted the last update I’ve done quite a bit of work on it. I still use it everyday and I’m very happy with it. (And it still, unfortunately, depends on a low-level python integration for Life360 which isn’t an official package.)

Besides additional error handling, which I’ve added as I’ve monitored what can go wrong with the communications to/from Life360, I also recently added a feature that can fire an event if the time between updates for members goes beyond a configurable threshold. I use this in an automation to send myself an e-mail if this happens, which it does from time to time when the Life360 app on someone’s phone stops updating the Life360 server. The automation looks like this:

- alias: Life360 Overdue Update
  trigger:
    platform: event
    event_type: device_tracker.life360_update_overdue
  action:
    service: notify.email_phil
    data_template:
      title: Life360 update overdue
      message: >
        Update for {{ state_attr(trigger.event.data.entity_id, 'friendly_name') }} is overdue.

And here’s what the platform configuration looks like:

device_tracker:
  - platform: life360
    username: life360_user_name
    password: !secret life360_password
    interval_seconds: 10
    max_update_wait:
      minutes: 30

And following is the code. I’m open to any comments, suggestions, criticisms, etc.

import datetime as dt
from requests import HTTPError, ConnectionError, Timeout
from json.decoder import JSONDecodeError
import logging
import voluptuous as vol
from homeassistant.components.device_tracker import (
    PLATFORM_SCHEMA, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_time_interval
from homeassistant import util

_LOGGER = logging.getLogger(__name__)

CONF_MAX_UPDATE_WAIT = 'max_update_wait'
_AUTHORIZATION_TOKEN = 'cFJFcXVnYWJSZXRyZTRFc3RldGhlcnVmcmVQdW1hbUV4dWNyRU'\
                       'h1YzptM2ZydXBSZXRSZXN3ZXJFQ2hBUHJFOTZxYWtFZHI0Vg=='

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_USERNAME): cv.string,
    vol.Required(CONF_PASSWORD): cv.string,
    vol.Optional(CONF_MAX_UPDATE_WAIT): vol.All(
        cv.time_period, cv.positive_timedelta)
})

def setup_scanner(hass, config, see, discovery_info=None):
    from pylife360 import life360
    api = life360(_AUTHORIZATION_TOKEN,
                  config[CONF_USERNAME], config[CONF_PASSWORD])
    if not api.authenticate(timeout=(3.05, 5)):
        _LOGGER.error('Authentication failed!')
        return False

    _LOGGER.info('Authentication successful!')
    interval = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
    max_update_wait = config.get(CONF_MAX_UPDATE_WAIT)
    Life360Scanner(hass, see, interval, max_update_wait, api)
    return True

class Life360Scanner(object):
    def __init__(self, hass, see, interval, max_update_wait, api):
        self._hass = hass
        self._see = see
        self._max_update_wait = max_update_wait
        self._api = api
        self._dev_data = {}
        self._started = util.dt.utcnow()
        track_time_interval(self._hass, self._update_life360, interval)

    def _update_life360(self, now=None):
        excs = (HTTPError, ConnectionError, Timeout, JSONDecodeError)
        def exc_msg(exc, msg=None, extra=None):
            _msg = '{}: {}'.format(exc.__class__.__name__, str(exc))
            if msg:
                _msg = '{}: '.format(msg) + _msg
            if extra:
                _msg += '; {}'.format(extra)
            _LOGGER.error(_msg)

        _LOGGER.debug('Checking members.')
        try:
            circles = self._api.get_circles(timeout=(3.05, 5))
        except excs as exc:
            exc_msg(exc, 'get_circles')
            return
        for circle in circles:
            try:
                members = self._api.get_circle(
                    circle['id'], timeout=(3.05, 5))['members']
            except excs as exc:
                exc_msg(exc, 'get_circle')
                continue
            for m in members:
                try:
                    dev_id = util.slugify(
                        '_'.join([m['firstName'], m['lastName']])
                           .replace('-', '_'))
                    prev_update, reported = self._dev_data.get(
                        dev_id, (None, False))

                    loc = m.get('location')
                    last_update = (None if not loc else
                        util.dt.utc_from_timestamp(
                            float(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': dev_id})
                            reported = True
                        elif not overdue:
                            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)
                        lat = float(loc['latitude'])
                        lon = float(loc['longitude'])
                        loc_name = loc['name']
                        # 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'
                        attrs = {
                            'last_update' : str(util.dt.as_local(last_update)),
                            'circle': circle['name']
                        }
                        self._see(dev_id=dev_id, location_name=loc_name,
                                  gps=(lat, lon),
                                  gps_accuracy=round(float(loc['accuracy'])),
                                  battery=round(float(loc['battery'])),
                                  attributes=attrs)

                    self._dev_data[dev_id] = (
                        last_update or prev_update, reported)

                except Exception as exc:
                    exc_msg(exc, extra='m = {}'.format(m))

I created a new topic for my custom integration. I figured I should stop distracting from the existing integrations discussed in this topic. If you have any interest in using my integration, see:

Hi @editter, I successfully configured your addon in my Hassi.io (v 0.69.1)
it works great both in MQTT and HTTP

but I’m trying to add a sensor to monitor the devices battery level
and I can’t figure out how to do that reading this page

is it possibile? can you help me?

thank you in advance

Is this what you are looking for?

sensor:
  - platform: template
    sensors:
      eric_battery:
        friendly_name: Eric Battery
        unit_of_measurement: '%'
        value_template: '{{ states.device_tracker.eric_phone.attributes.battery|round }}'

I can’t believe that was so simple!
I’ve tried in so many different ways and no one was working

thank you so much!

1 Like

+1 for including this Life360 Device Tracker Platform in the default. I’ve been using it for a few weeks without any issues. It’s much more reliable than any of the other device tracker platforms that I’ve played around with.

Add me, too. I have been using Life360 on my family’s phones for more than a year and very pleased with the accuracy. I would very much like to integrate it into Home Assistant, but reading through 200+ messages on the topic gives me a headache.

If all the bugs are out of the platform, would it be possible for one of those who have done the integration to write a step-by-step tutorial?

I have also been using Life360 for a long time and have had it in HASS for almost as long. The custom component works perfectly.

This is the one I am using:

Please put a version number or date in your code comments. I don’t know if what I have working is older or not.

It’s had a version number (and support for the Custom Updater) since Sept. 7. You can find the latest information about this custom component here.

Thanks for the quick response. I did not see any versioning or date in the code posted in the thread, which is why I asked. It didn’t occur to me to look for you on the git.
In case I haven’t said it before, thanks. I had been trying to get tracking to work using different platforms and installing your .py files (copied from the thread) and a few lines in configuration.yaml and I now have working badges on my home page.

Now I have a mess of Ping tracker badges to clean up.

Still learning,
Steve

I never heard of Custom Updater. How do I use it?

You’re very welcome. I’m glad you’re finding this custom component useful.

Sorry about the community topics. I was very unfamiliar with this forum, as well as HA, when I first wrote this, and I wasn’t really on github yet either. If I knew then what I know now, I would have made sure the first post on my topic pointed to something that would stay up to date. Oh well, live and learn! :slight_smile:

BTW, I also have a custom composite tracker you might be interested in (and I’m working on a variant of the standard ping tracker.) Check out the top-level doc on my github for details on these and other potentially useful tidbits. :wink: