Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 91 additions & 71 deletions homeassistant/components/device_tracker/tile.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,22 @@
https://home-assistant.io/components/device_tracker.tile/
"""
import logging
from datetime import timedelta

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_USERNAME, CONF_MONITORED_VARIABLES, CONF_PASSWORD)
from homeassistant.helpers.event import track_utc_time_change
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__)

REQUIREMENTS = ['pytile==1.1.0']
REQUIREMENTS = ['pytile==2.0.2']

CLIENT_UUID_CONFIG_FILE = '.tile.conf'
DEFAULT_ICON = 'mdi:bluetooth'
DEVICE_TYPES = ['PHONE', 'TILE']

ATTR_ALTITUDE = 'altitude'
Expand All @@ -34,89 +32,111 @@

CONF_SHOW_INACTIVE = 'show_inactive'

DEFAULT_ICON = 'mdi:bluetooth'
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):
vol.Optional(CONF_MONITORED_VARIABLES, default=DEVICE_TYPES):
vol.All(cv.ensure_list, [vol.In(DEVICE_TYPES)]),
})


def setup_scanner(hass, config: dict, see, discovery_info=None):
async def async_setup_scanner(hass, config, async_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):
from pytile import Client

websession = aiohttp_client.async_get_clientsession(hass)

config_data = await hass.async_add_job(
load_json, hass.config.path(CLIENT_UUID_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}
config_saved = await hass.async_add_job(
save_json, hass.config.path(CLIENT_UUID_CONFIG_FILE), config_data)
if not config_saved:
_LOGGER.error('Failed to save the client UUID')

scanner = TileScanner(
client, hass, async_see, config[CONF_MONITORED_VARIABLES],
config[CONF_SHOW_INACTIVE])
return await scanner.async_init()


class TileScanner(object):
"""Define an object to retrieve Tile data."""

def __init__(self, client, hass, async_see, types, show_inactive):
"""Initialize."""
from pytile import Client

_LOGGER.debug('Received configuration data: %s', config)
self._async_see = async_see
self._client = client
self._hass = hass
self._show_inactive = show_inactive
self._types = types

# 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])
async def async_init(self):
"""Further initialize connection to the Tile servers."""
from pytile.errors import TileError

if not save_json(
hass.config.path(CLIENT_UUID_CONFIG_FILE),
{'client_uuid': self._client.client_uuid}):
_LOGGER.error("Failed to save configuration file")
try:
await self._client.async_init()
except TileError as err:
_LOGGER.error('Unable to set up Tile scanner: %s', err)
return False

_LOGGER.debug('Client UUID: %s', self._client.client_uuid)
_LOGGER.debug('User UUID: %s', self._client.user_uuid)
await self._async_update()

self._show_inactive = config.get(CONF_SHOW_INACTIVE)
self._types = config.get(CONF_MONITORED_VARIABLES)
async_track_time_interval(
self._hass, self._async_update, DEFAULT_SCAN_INTERVAL)

self.devices = {}
self.see = see
return True

track_utc_time_change(
hass, self._update_info, second=range(0, 60, 30))
async def _async_update(self, now=None):
"""Update info from Tile."""
from pytile.errors import SessionExpiredError, TileError

self._update_info()
_LOGGER.debug('Updating Tile data')

def _update_info(self, now=None) -> None:
"""Update the device info."""
self.devices = self._client.get_tiles(
type_whitelist=self._types, show_inactive=self._show_inactive)
try:
await self._client.asayn_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 self.devices:
if not tiles:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation line under-indented for visual indent

_LOGGER.warning('No Tiles found')
return

for dev in self.devices:
dev_id = 'tile_{0}'.format(slugify(dev['name']))
lat = dev['tileState']['latitude']
lon = dev['tileState']['longitude']

attrs = {
ATTR_ALTITUDE: dev['tileState']['altitude'],
ATTR_CONNECTION_STATE: dev['tileState']['connection_state'],
ATTR_IS_DEAD: dev['is_dead'],
ATTR_IS_LOST: dev['tileState']['is_lost'],
ATTR_RING_STATE: dev['tileState']['ring_state'],
ATTR_VOIP_STATE: dev['tileState']['voip_state'],
}

self.see(
dev_id=dev_id,
gps=(lat, lon),
attributes=attrs,
icon=DEFAULT_ICON
)
for tile in tiles:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continuation line under-indented for visual indent

await self._async_see(
dev_id='tile_{0}'.format(slugify(tile['name'])),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unexpected indentation
missing whitespace around operator

gps=(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing whitespace around operator

tile['tileState']['latitude'],
tile['tileState']['longitude']
),
attributes={
ATTR_ALTITUDE: tile['tileState']['altitude'],
ATTR_CONNECTION_STATE:
tile['tileState']['connection_state'],
ATTR_IS_DEAD: tile['is_dead'],
ATTR_IS_LOST: tile['tileState']['is_lost'],
ATTR_RING_STATE: tile['tileState']['ring_state'],
ATTR_VOIP_STATE: tile['tileState']['voip_state'],
},
icon=DEFAULT_ICON)
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1113,7 +1113,7 @@ pythonegardia==1.0.39
pythonwhois==2.4.3

# homeassistant.components.device_tracker.tile
pytile==1.1.0
pytile==2.0.2

# homeassistant.components.climate.touchline
pytouchline==0.7
Expand Down