"""Support for TileĀ® Bluetooth trackers.""" import logging from datetime import timedelta import voluptuous as vol from homeassistant.components.device_tracker import PLATFORM_SCHEMA from homeassistant.const import CONF_USERNAME, CONF_MONITORED_VARIABLES, CONF_PASSWORD from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.event import async_track_time_interval from homeassistant.util import slugify from homeassistant.util.json import load_json, save_json _LOGGER = logging.getLogger(__name__) CLIENT_UUID_CONFIG_FILE = ".tile.conf" DEVICE_TYPES = ["PHONE", "TILE"] ATTR_ALTITUDE = "altitude" ATTR_CONNECTION_STATE = "connection_state" ATTR_IS_DEAD = "is_dead" ATTR_IS_LOST = "is_lost" ATTR_RING_STATE = "ring_state" ATTR_VOIP_STATE = "voip_state" ATTR_TILE_ID = "tile_identifier" ATTR_TILE_NAME = "tile_name" CONF_SHOW_INACTIVE = "show_inactive" DEFAULT_ICON = "mdi:view-grid" DEFAULT_SCAN_INTERVAL = timedelta(minutes=2) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Optional(CONF_SHOW_INACTIVE, default=False): cv.boolean, vol.Optional(CONF_MONITORED_VARIABLES, default=DEVICE_TYPES): vol.All( cv.ensure_list, [vol.In(DEVICE_TYPES)] ), } ) async def async_setup_scanner(hass, config, async_see, discovery_info=None): """Validate the configuration and return a Tile scanner.""" from pytile import Client websession = aiohttp_client.async_get_clientsession(hass) config_file = hass.config.path( ".{}{}".format(slugify(config[CONF_USERNAME]), CLIENT_UUID_CONFIG_FILE) ) config_data = await hass.async_add_job(load_json, config_file) if config_data: client = Client( config[CONF_USERNAME], config[CONF_PASSWORD], websession, client_uuid=config_data["client_uuid"], ) else: client = Client(config[CONF_USERNAME], config[CONF_PASSWORD], websession) config_data = {"client_uuid": client.client_uuid} await hass.async_add_job(save_json, config_file, config_data) scanner = TileScanner( client, hass, async_see, config[CONF_MONITORED_VARIABLES], config[CONF_SHOW_INACTIVE], ) return await scanner.async_init() class TileScanner: """Define an object to retrieve Tile data.""" def __init__(self, client, hass, async_see, types, show_inactive): """Initialize.""" self._async_see = async_see self._client = client self._hass = hass self._show_inactive = show_inactive self._types = types async def async_init(self): """Further initialize connection to the Tile servers.""" from pytile.errors import TileError try: await self._client.async_init() except TileError as err: _LOGGER.error("Unable to set up Tile scanner: %s", err) return False await self._async_update() async_track_time_interval(self._hass, self._async_update, DEFAULT_SCAN_INTERVAL) return True async def _async_update(self, now=None): """Update info from Tile.""" from pytile.errors import SessionExpiredError, TileError _LOGGER.debug("Updating Tile data") try: await self._client.async_init() tiles = await self._client.tiles.all( whitelist=self._types, show_inactive=self._show_inactive ) except SessionExpiredError: _LOGGER.info("Session expired; trying again shortly") return except TileError as err: _LOGGER.error("There was an error while updating: %s", err) return if not tiles: _LOGGER.warning("No Tiles found") return for tile in tiles: await self._async_see( dev_id="tile_{0}".format(slugify(tile["tile_uuid"])), gps=( tile["last_tile_state"]["latitude"], tile["last_tile_state"]["longitude"], ), attributes={ ATTR_ALTITUDE: tile["last_tile_state"]["altitude"], ATTR_CONNECTION_STATE: tile["last_tile_state"]["connection_state"], ATTR_IS_DEAD: tile["is_dead"], ATTR_IS_LOST: tile["last_tile_state"]["is_lost"], ATTR_RING_STATE: tile["last_tile_state"]["ring_state"], ATTR_VOIP_STATE: tile["last_tile_state"]["voip_state"], ATTR_TILE_ID: tile["tile_uuid"], ATTR_TILE_NAME: tile["name"], }, icon=DEFAULT_ICON, )