core/homeassistant/components/tile/device_tracker.py

145 lines
4.7 KiB
Python

"""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,
)