Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
161 changes: 90 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,110 @@

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:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Move this up to only wrap the call(s) that we expect to error.

_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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SyntaxError: invalid syntax

except SessionExpiredError:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Only wrap the call that you expect to error with try... except. I think that's await self._client.tiles.all in this case.

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.info('Session expired; trying again shortly')
return
except TileError as err:

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.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={

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

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