Home Assistant Unofficial Reference 2024.12.1
device_tracker.py
Go to the documentation of this file.
1 """Support for Google Maps location sharing."""
2 
3 from __future__ import annotations
4 
5 from datetime import timedelta
6 import logging
7 
8 from locationsharinglib import Service
9 from locationsharinglib.locationsharinglibexceptions import InvalidCookies
10 import voluptuous as vol
11 
13  PLATFORM_SCHEMA as DEVICE_TRACKER_PLATFORM_SCHEMA,
14  SeeCallback,
15  SourceType,
16 )
17 from homeassistant.const import (
18  ATTR_BATTERY_CHARGING,
19  ATTR_BATTERY_LEVEL,
20  ATTR_ID,
21  CONF_SCAN_INTERVAL,
22  CONF_USERNAME,
23 )
24 from homeassistant.core import HomeAssistant
26 from homeassistant.helpers.event import track_time_interval
27 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
28 from homeassistant.util import dt as dt_util, slugify
29 
30 _LOGGER = logging.getLogger(__name__)
31 
32 ATTR_ADDRESS = "address"
33 ATTR_FULL_NAME = "full_name"
34 ATTR_LAST_SEEN = "last_seen"
35 ATTR_NICKNAME = "nickname"
36 
37 CONF_MAX_GPS_ACCURACY = "max_gps_accuracy"
38 
39 CREDENTIALS_FILE = ".google_maps_location_sharing.cookies"
40 
41 # the parent "device_tracker" have marked the schemas as legacy, so this
42 # need to be refactored as part of a bigger rewrite.
43 PLATFORM_SCHEMA = DEVICE_TRACKER_PLATFORM_SCHEMA.extend(
44  {
45  vol.Required(CONF_USERNAME): cv.string,
46  vol.Optional(CONF_MAX_GPS_ACCURACY, default=100000): vol.Coerce(float),
47  }
48 )
49 
50 
52  hass: HomeAssistant,
53  config: ConfigType,
54  see: SeeCallback,
55  discovery_info: DiscoveryInfoType | None = None,
56 ) -> bool:
57  """Set up the Google Maps Location sharing scanner."""
58  scanner = GoogleMapsScanner(hass, config, see)
59  return scanner.success_init
60 
61 
63  """Representation of an Google Maps location sharing account."""
64 
65  def __init__(
66  self, hass: HomeAssistant, config: ConfigType, see: SeeCallback
67  ) -> None:
68  """Initialize the scanner."""
69  self.seesee = see
70  self.usernameusername = config[CONF_USERNAME]
71  self.max_gps_accuracymax_gps_accuracy = config[CONF_MAX_GPS_ACCURACY]
72  self.scan_intervalscan_interval = config.get(CONF_SCAN_INTERVAL) or timedelta(seconds=60)
73  self._prev_seen: dict[str, str] = {}
74 
75  credfile = f"{hass.config.path(CREDENTIALS_FILE)}.{slugify(self.username)}"
76  try:
77  self.serviceservice = Service(credfile, self.usernameusername)
78  self._update_info_update_info()
79 
80  track_time_interval(hass, self._update_info_update_info, self.scan_intervalscan_interval)
81 
82  self.success_initsuccess_init = True
83 
84  except InvalidCookies:
85  _LOGGER.error(
86  "The cookie file provided does not provide a valid session. Please"
87  " create another one and try again"
88  )
89  self.success_initsuccess_init = False
90 
91  def _update_info(self, now=None):
92  for person in self.serviceservice.get_all_people():
93  try:
94  dev_id = f"google_maps_{slugify(person.id)}"
95  except TypeError:
96  _LOGGER.warning("No location(s) shared with this account")
97  return
98 
99  if (
100  self.max_gps_accuracymax_gps_accuracy is not None
101  and person.accuracy > self.max_gps_accuracymax_gps_accuracy
102  ):
103  _LOGGER.debug(
104  (
105  "Ignoring %s update because expected GPS "
106  "accuracy %s is not met: %s"
107  ),
108  person.nickname,
109  self.max_gps_accuracymax_gps_accuracy,
110  person.accuracy,
111  )
112  continue
113 
114  last_seen = dt_util.as_utc(person.datetime)
115  if last_seen < self._prev_seen.get(dev_id, last_seen):
116  _LOGGER.debug(
117  "Ignoring %s update because timestamp is older than last timestamp",
118  person.nickname,
119  )
120  _LOGGER.debug("%s < %s", last_seen, self._prev_seen[dev_id])
121  continue
122  if last_seen == self._prev_seen.get(dev_id):
123  _LOGGER.debug(
124  "Ignoring %s update because timestamp "
125  "is the same as the last timestamp %s",
126  person.nickname,
127  last_seen,
128  )
129  continue
130  self._prev_seen[dev_id] = last_seen
131 
132  attrs = {
133  ATTR_ADDRESS: person.address,
134  ATTR_FULL_NAME: person.full_name,
135  ATTR_ID: person.id,
136  ATTR_LAST_SEEN: last_seen,
137  ATTR_NICKNAME: person.nickname,
138  ATTR_BATTERY_CHARGING: person.charging,
139  ATTR_BATTERY_LEVEL: person.battery_level,
140  }
141  self.seesee(
142  dev_id=dev_id,
143  gps=(person.latitude, person.longitude),
144  picture=person.picture_url,
145  source_type=SourceType.GPS,
146  gps_accuracy=person.accuracy,
147  attributes=attrs,
148  )
None __init__(self, HomeAssistant hass, ConfigType config, SeeCallback see)
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
bool setup_scanner(HomeAssistant hass, ConfigType config, SeeCallback see, DiscoveryInfoType|None discovery_info=None)