Improving icloud device tracker (#14078)

* Improving icloud device tracker

* Adding config validations for new values

* Adding config validations for new values

* Moving icloud specific setup to platform schema. Setting default in platform schema.
This commit is contained in:
Evgeniy 2018-05-08 14:42:57 -07:00 committed by Fabian Affolter
parent 10505d542a
commit 9c7523d7b0

View file

@ -24,8 +24,9 @@ _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pyicloud==0.9.1'] REQUIREMENTS = ['pyicloud==0.9.1']
CONF_IGNORED_DEVICES = 'ignored_devices'
CONF_ACCOUNTNAME = 'account_name' CONF_ACCOUNTNAME = 'account_name'
CONF_MAX_INTERVAL = 'max_interval'
CONF_GPS_ACCURACY_THRESHOLD = 'gps_accuracy_threshold'
# entity attributes # entity attributes
ATTR_ACCOUNTNAME = 'account_name' ATTR_ACCOUNTNAME = 'account_name'
@ -64,13 +65,15 @@ DEVICESTATUSCODES = {
SERVICE_SCHEMA = vol.Schema({ SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ACCOUNTNAME): vol.All(cv.ensure_list, [cv.slugify]), vol.Optional(ATTR_ACCOUNTNAME): vol.All(cv.ensure_list, [cv.slugify]),
vol.Optional(ATTR_DEVICENAME): cv.slugify, vol.Optional(ATTR_DEVICENAME): cv.slugify,
vol.Optional(ATTR_INTERVAL): cv.positive_int, vol.Optional(ATTR_INTERVAL): cv.positive_int
}) })
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(ATTR_ACCOUNTNAME): cv.slugify, vol.Optional(ATTR_ACCOUNTNAME): cv.slugify,
vol.Optional(CONF_MAX_INTERVAL, default=30): cv.positive_int,
vol.Optional(CONF_GPS_ACCURACY_THRESHOLD, default=1000): cv.positive_int
}) })
@ -79,8 +82,11 @@ def setup_scanner(hass, config: dict, see, discovery_info=None):
username = config.get(CONF_USERNAME) username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD) password = config.get(CONF_PASSWORD)
account = config.get(CONF_ACCOUNTNAME, slugify(username.partition('@')[0])) account = config.get(CONF_ACCOUNTNAME, slugify(username.partition('@')[0]))
max_interval = config.get(CONF_MAX_INTERVAL)
gps_accuracy_threshold = config.get(CONF_GPS_ACCURACY_THRESHOLD)
icloudaccount = Icloud(hass, username, password, account, see) icloudaccount = Icloud(hass, username, password, account, max_interval,
gps_accuracy_threshold, see)
if icloudaccount.api is not None: if icloudaccount.api is not None:
ICLOUDTRACKERS[account] = icloudaccount ICLOUDTRACKERS[account] = icloudaccount
@ -96,6 +102,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None):
for account in accounts: for account in accounts:
if account in ICLOUDTRACKERS: if account in ICLOUDTRACKERS:
ICLOUDTRACKERS[account].lost_iphone(devicename) ICLOUDTRACKERS[account].lost_iphone(devicename)
hass.services.register(DOMAIN, 'icloud_lost_iphone', lost_iphone, hass.services.register(DOMAIN, 'icloud_lost_iphone', lost_iphone,
schema=SERVICE_SCHEMA) schema=SERVICE_SCHEMA)
@ -106,6 +113,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None):
for account in accounts: for account in accounts:
if account in ICLOUDTRACKERS: if account in ICLOUDTRACKERS:
ICLOUDTRACKERS[account].update_icloud(devicename) ICLOUDTRACKERS[account].update_icloud(devicename)
hass.services.register(DOMAIN, 'icloud_update', update_icloud, hass.services.register(DOMAIN, 'icloud_update', update_icloud,
schema=SERVICE_SCHEMA) schema=SERVICE_SCHEMA)
@ -115,6 +123,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None):
for account in accounts: for account in accounts:
if account in ICLOUDTRACKERS: if account in ICLOUDTRACKERS:
ICLOUDTRACKERS[account].reset_account_icloud() ICLOUDTRACKERS[account].reset_account_icloud()
hass.services.register(DOMAIN, 'icloud_reset_account', hass.services.register(DOMAIN, 'icloud_reset_account',
reset_account_icloud, schema=SERVICE_SCHEMA) reset_account_icloud, schema=SERVICE_SCHEMA)
@ -137,7 +146,8 @@ def setup_scanner(hass, config: dict, see, discovery_info=None):
class Icloud(DeviceScanner): class Icloud(DeviceScanner):
"""Representation of an iCloud account.""" """Representation of an iCloud account."""
def __init__(self, hass, username, password, name, see): def __init__(self, hass, username, password, name, max_interval,
gps_accuracy_threshold, see):
"""Initialize an iCloud account.""" """Initialize an iCloud account."""
self.hass = hass self.hass = hass
self.username = username self.username = username
@ -148,6 +158,8 @@ class Icloud(DeviceScanner):
self.seen_devices = {} self.seen_devices = {}
self._overridestates = {} self._overridestates = {}
self._intervals = {} self._intervals = {}
self._max_interval = max_interval
self._gps_accuracy_threshold = gps_accuracy_threshold
self.see = see self.see = see
self._trusted_device = None self._trusted_device = None
@ -348,7 +360,7 @@ class Icloud(DeviceScanner):
self._overridestates[devicename] = None self._overridestates[devicename] = None
if currentzone is not None: if currentzone is not None:
self._intervals[devicename] = 30 self._intervals[devicename] = self._max_interval
return return
if mindistance is None: if mindistance is None:
@ -363,7 +375,6 @@ class Icloud(DeviceScanner):
if interval > 180: if interval > 180:
# Three hour drive? This is far enough that they might be flying # Three hour drive? This is far enough that they might be flying
# home - check every half hour
interval = 30 interval = 30
if battery is not None and battery <= 33 and mindistance > 3: if battery is not None and battery <= 33 and mindistance > 3:
@ -403,22 +414,24 @@ class Icloud(DeviceScanner):
status = device.status(DEVICESTATUSSET) status = device.status(DEVICESTATUSSET)
battery = status.get('batteryLevel', 0) * 100 battery = status.get('batteryLevel', 0) * 100
location = status['location'] location = status['location']
if location: if location and location['horizontalAccuracy']:
self.determine_interval( horizontal_accuracy = int(location['horizontalAccuracy'])
devicename, location['latitude'], if horizontal_accuracy < self._gps_accuracy_threshold:
location['longitude'], battery) self.determine_interval(
interval = self._intervals.get(devicename, 1) devicename, location['latitude'],
attrs[ATTR_INTERVAL] = interval location['longitude'], battery)
accuracy = location['horizontalAccuracy'] interval = self._intervals.get(devicename, 1)
kwargs['dev_id'] = dev_id attrs[ATTR_INTERVAL] = interval
kwargs['host_name'] = status['name'] accuracy = location['horizontalAccuracy']
kwargs['gps'] = (location['latitude'], kwargs['dev_id'] = dev_id
location['longitude']) kwargs['host_name'] = status['name']
kwargs['battery'] = battery kwargs['gps'] = (location['latitude'],
kwargs['gps_accuracy'] = accuracy location['longitude'])
kwargs[ATTR_ATTRIBUTES] = attrs kwargs['battery'] = battery
self.see(**kwargs) kwargs['gps_accuracy'] = accuracy
self.seen_devices[devicename] = True kwargs[ATTR_ATTRIBUTES] = attrs
self.see(**kwargs)
self.seen_devices[devicename] = True
except PyiCloudNoDevicesException: except PyiCloudNoDevicesException:
_LOGGER.error("No iCloud Devices found") _LOGGER.error("No iCloud Devices found")
@ -434,7 +447,7 @@ class Icloud(DeviceScanner):
device.play_sound() device.play_sound()
def update_icloud(self, devicename=None): def update_icloud(self, devicename=None):
"""Authenticate against iCloud and scan for devices.""" """Request device information from iCloud and update device_tracker."""
from pyicloud.exceptions import PyiCloudNoDevicesException from pyicloud.exceptions import PyiCloudNoDevicesException
if self.api is None: if self.api is None:
@ -443,13 +456,13 @@ class Icloud(DeviceScanner):
try: try:
if devicename is not None: if devicename is not None:
if devicename in self.devices: if devicename in self.devices:
self.devices[devicename].location() self.update_device(devicename)
else: else:
_LOGGER.error("devicename %s unknown for account %s", _LOGGER.error("devicename %s unknown for account %s",
devicename, self._attrs[ATTR_ACCOUNTNAME]) devicename, self._attrs[ATTR_ACCOUNTNAME])
else: else:
for device in self.devices: for device in self.devices:
self.devices[device].location() self.update_device(device)
except PyiCloudNoDevicesException: except PyiCloudNoDevicesException:
_LOGGER.error("No iCloud Devices found") _LOGGER.error("No iCloud Devices found")