From 79001fc361b6adb9377638acd41dd3b1c64bc0f9 Mon Sep 17 00:00:00 2001 From: Aaron Bach Date: Sat, 11 Nov 2017 15:21:03 -0700 Subject: [PATCH] =?UTF-8?q?Adds=20support=20for=20Tile=C2=AE=20Bluetooth?= =?UTF-8?q?=20trackers=20(#10478)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial work in place * Added new attributes + client UUID storage * Wrapped up * Collaborator-requested changes --- .coveragerc | 1 + .../components/device_tracker/tile.py | 124 ++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 128 insertions(+) create mode 100644 homeassistant/components/device_tracker/tile.py diff --git a/.coveragerc b/.coveragerc index 3bfd983dc30..390e57e2e31 100644 --- a/.coveragerc +++ b/.coveragerc @@ -325,6 +325,7 @@ omit = homeassistant/components/device_tracker/thomson.py homeassistant/components/device_tracker/tomato.py homeassistant/components/device_tracker/tado.py + homeassistant/components/device_tracker/tile.py homeassistant/components/device_tracker/tplink.py homeassistant/components/device_tracker/trackr.py homeassistant/components/device_tracker/ubus.py diff --git a/homeassistant/components/device_tracker/tile.py b/homeassistant/components/device_tracker/tile.py new file mode 100644 index 00000000000..f27a950a49f --- /dev/null +++ b/homeassistant/components/device_tracker/tile.py @@ -0,0 +1,124 @@ +""" +Support for TileĀ® Bluetooth trackers. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/device_tracker.tile/ +""" +import logging + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.device_tracker import ( + PLATFORM_SCHEMA, DeviceScanner) +from homeassistant.const import ( + CONF_USERNAME, CONF_MONITORED_VARIABLES, CONF_PASSWORD) +from homeassistant.helpers.event import track_utc_time_change +from homeassistant.util import slugify +from homeassistant.util.json import load_json, save_json + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ['pytile==1.0.0'] + +CLIENT_UUID_CONFIG_FILE = '.tile.conf' +DEFAULT_ICON = 'mdi:bluetooth' +DEVICE_TYPES = ['PHONE', 'TILE'] + +ATTR_ALTITUDE = 'altitude' +ATTR_CONNECTION_STATE = 'connection_state' +ATTR_IS_DEAD = 'is_dead' +ATTR_IS_LOST = 'is_lost' +ATTR_LAST_SEEN = 'last_seen' +ATTR_LAST_UPDATED = 'last_updated' +ATTR_RING_STATE = 'ring_state' +ATTR_VOIP_STATE = 'voip_state' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_MONITORED_VARIABLES): + vol.All(cv.ensure_list, [vol.In(DEVICE_TYPES)]), +}) + + +def setup_scanner(hass, config: dict, see, discovery_info=None): + """Validate the configuration and return a Tile scanner.""" + TileDeviceScanner(hass, config, see) + return True + + +class TileDeviceScanner(DeviceScanner): + """Define a device scanner for Tiles.""" + + def __init__(self, hass, config, see): + """Initialize.""" + from pytile import Client + + _LOGGER.debug('Received configuration data: %s', config) + + # Load the client UUID (if it exists): + config_data = load_json(hass.config.path(CLIENT_UUID_CONFIG_FILE)) + if config_data: + _LOGGER.debug('Using existing client UUID') + self._client = Client( + config[CONF_USERNAME], + config[CONF_PASSWORD], + config_data['client_uuid']) + else: + _LOGGER.debug('Generating new client UUID') + self._client = Client( + config[CONF_USERNAME], + config[CONF_PASSWORD]) + + if not save_json( + hass.config.path(CLIENT_UUID_CONFIG_FILE), + {'client_uuid': self._client.client_uuid}): + _LOGGER.error("Failed to save configuration file") + + _LOGGER.debug('Client UUID: %s', self._client.client_uuid) + _LOGGER.debug('User UUID: %s', self._client.user_uuid) + + self._types = config.get(CONF_MONITORED_VARIABLES) + + self.devices = {} + self.see = see + + track_utc_time_change( + hass, self._update_info, second=range(0, 60, 30)) + + self._update_info() + + def _update_info(self, now=None) -> None: + """Update the device info.""" + device_data = self._client.get_tiles(type_whitelist=self._types) + + try: + self.devices = device_data['result'] + except KeyError: + _LOGGER.warning('No Tiles found') + _LOGGER.debug(device_data) + return + + for info in self.devices.values(): + dev_id = 'tile_{0}'.format(slugify(info['name'])) + lat = info['tileState']['latitude'] + lon = info['tileState']['longitude'] + + attrs = { + ATTR_ALTITUDE: info['tileState']['altitude'], + ATTR_CONNECTION_STATE: info['tileState']['connection_state'], + ATTR_IS_DEAD: info['is_dead'], + ATTR_IS_LOST: info['tileState']['is_lost'], + ATTR_LAST_SEEN: info['tileState']['timestamp'], + ATTR_LAST_UPDATED: device_data['timestamp_ms'], + ATTR_RING_STATE: info['tileState']['ring_state'], + ATTR_VOIP_STATE: info['tileState']['voip_state'], + } + + self.see( + dev_id=dev_id, + gps=(lat, lon), + attributes=attrs, + icon=DEFAULT_ICON + ) diff --git a/requirements_all.txt b/requirements_all.txt index b9d306f2e81..9f4a911b6b1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -893,6 +893,9 @@ pythonegardia==1.0.22 # homeassistant.components.sensor.whois pythonwhois==2.4.3 +# homeassistant.components.device_tracker.tile +pytile==1.0.0 + # homeassistant.components.device_tracker.trackr pytrackr==0.0.5