Allow device_tracker and sensor entity for google travel times (#2479)

* Allow owntracks entity for google travel times

* Added ability to use sensor state as location

* Added zone checks for google travel timesg

* Updated to use global constents and the location helper

* Fixed type in method name and removed redundant validation

* Changed domain condition to be a bit more elegant

* Updated to allow friendly name in any instance including the config

* Fixed bad python syntax and used helper methods
This commit is contained in:
Brent 2016-07-12 23:46:11 -05:00 committed by Paulus Schoutsen
parent e1db639317
commit b9cadbecaa

View File

@ -10,9 +10,13 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.const import CONF_API_KEY, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.const import (
CONF_API_KEY, TEMP_CELSIUS, TEMP_FAHRENHEIT,
EVENT_HOMEASSISTANT_START, ATTR_LATITUDE, ATTR_LONGITUDE)
from homeassistant.util import Throttle from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.location as location
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -65,6 +69,8 @@ PLATFORM_SCHEMA = vol.Schema({
})) }))
}) })
TRACKABLE_DOMAINS = ["device_tracker", "sensor", "zone"]
def convert_time_to_utc(timestr): def convert_time_to_utc(timestr):
"""Take a string like 08:00:00 and convert it to a unix timestamp.""" """Take a string like 08:00:00 and convert it to a unix timestamp."""
@ -78,36 +84,44 @@ def convert_time_to_utc(timestr):
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Setup the travel time platform.""" """Setup the travel time platform."""
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
options = config.get(CONF_OPTIONS) def run_setup(event):
"""Delay the setup until home assistant is fully initialized.
if options.get('units') is None: This allows any entities to be created already
if hass.config.temperature_unit is TEMP_CELSIUS: """
options['units'] = 'metric' options = config.get(CONF_OPTIONS)
elif hass.config.temperature_unit is TEMP_FAHRENHEIT:
options['units'] = 'imperial'
travel_mode = config.get(CONF_TRAVEL_MODE) if options.get('units') is None:
mode = options.get(CONF_MODE) if hass.config.temperature_unit is TEMP_CELSIUS:
options['units'] = 'metric'
elif hass.config.temperature_unit is TEMP_FAHRENHEIT:
options['units'] = 'imperial'
if travel_mode is not None: travel_mode = config.get(CONF_TRAVEL_MODE)
wstr = ("Google Travel Time: travel_mode is deprecated, please add " mode = options.get(CONF_MODE)
"mode to the options dictionary instead!")
_LOGGER.warning(wstr)
if mode is None:
options[CONF_MODE] = travel_mode
titled_mode = options.get(CONF_MODE).title() if travel_mode is not None:
formatted_name = "Google Travel Time - {}".format(titled_mode) wstr = ("Google Travel Time: travel_mode is deprecated, please "
name = config.get(CONF_NAME, formatted_name) "add mode to the options dictionary instead!")
api_key = config.get(CONF_API_KEY) _LOGGER.warning(wstr)
origin = config.get(CONF_ORIGIN) if mode is None:
destination = config.get(CONF_DESTINATION) options[CONF_MODE] = travel_mode
sensor = GoogleTravelTimeSensor(name, api_key, origin, destination, titled_mode = options.get(CONF_MODE).title()
options) formatted_name = "Google Travel Time - {}".format(titled_mode)
name = config.get(CONF_NAME, formatted_name)
api_key = config.get(CONF_API_KEY)
origin = config.get(CONF_ORIGIN)
destination = config.get(CONF_DESTINATION)
if sensor.valid_api_connection: sensor = GoogleTravelTimeSensor(hass, name, api_key, origin,
add_devices_callback([sensor]) destination, options)
if sensor.valid_api_connection:
add_devices_callback([sensor])
# Wait until start event is sent to load this component.
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup)
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
@ -115,15 +129,25 @@ class GoogleTravelTimeSensor(Entity):
"""Representation of a tavel time sensor.""" """Representation of a tavel time sensor."""
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments
def __init__(self, name, api_key, origin, destination, options): def __init__(self, hass, name, api_key, origin, destination, options):
"""Initialize the sensor.""" """Initialize the sensor."""
self._hass = hass
self._name = name self._name = name
self._options = options self._options = options
self._origin = origin
self._destination = destination
self._matrix = None self._matrix = None
self.valid_api_connection = True self.valid_api_connection = True
# Check if location is a trackable entity
if origin.split('.', 1)[0] in TRACKABLE_DOMAINS:
self._origin_entity_id = origin
else:
self._origin = origin
if destination.split('.', 1)[0] in TRACKABLE_DOMAINS:
self._destination_entity_id = destination
else:
self._destination = destination
import googlemaps import googlemaps
self._client = googlemaps.Client(api_key, timeout=10) self._client = googlemaps.Client(api_key, timeout=10)
try: try:
@ -136,6 +160,9 @@ class GoogleTravelTimeSensor(Entity):
@property @property
def state(self): def state(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
if self._matrix is None:
return None
_data = self._matrix['rows'][0]['elements'][0] _data = self._matrix['rows'][0]['elements'][0]
if 'duration_in_traffic' in _data: if 'duration_in_traffic' in _data:
return round(_data['duration_in_traffic']['value']/60) return round(_data['duration_in_traffic']['value']/60)
@ -151,6 +178,9 @@ class GoogleTravelTimeSensor(Entity):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """Return the state attributes."""
if self._matrix is None:
return None
res = self._matrix.copy() res = self._matrix.copy()
res.update(self._options) res.update(self._options)
del res['rows'] del res['rows']
@ -186,6 +216,64 @@ class GoogleTravelTimeSensor(Entity):
elif atime is not None: elif atime is not None:
options_copy['arrival_time'] = atime options_copy['arrival_time'] = atime
self._matrix = self._client.distance_matrix(self._origin, # Convert device_trackers to google friendly location
self._destination, if hasattr(self, '_origin_entity_id'):
**options_copy) self._origin = self._get_location_from_entity(
self._origin_entity_id
)
if hasattr(self, '_destination_entity_id'):
self._destination = self._get_location_from_entity(
self._destination_entity_id
)
self._destination = self._resolve_zone(self._destination)
self._origin = self._resolve_zone(self._origin)
if self._destination is not None and self._origin is not None:
self._matrix = self._client.distance_matrix(self._origin,
self._destination,
**options_copy)
def _get_location_from_entity(self, entity_id):
"""Get the location from the entity state or attributes."""
entity = self._hass.states.get(entity_id)
if entity is None:
_LOGGER.error("Unable to find entity %s", entity_id)
self.valid_api_connection = False
return None
# Check if device is in a zone
zone_entity = self._hass.states.get("zone.%s" % entity.state)
if location.has_location(zone_entity):
_LOGGER.debug(
"%s is in %s, getting zone location.",
entity_id, zone_entity.entity_id
)
return self._get_location_from_attributes(zone_entity)
# If zone was not found in state then use the state as the location
if entity_id.startswith("sensor."):
return entity.state
# For everything else look for location attributes
if location.has_location(entity):
return self._get_location_from_attributes(entity)
# When everything fails just return nothing
return None
@staticmethod
def _get_location_from_attributes(entity):
"""Get the lat/long string from an entities attributes."""
attr = entity.attributes
return "%s,%s" % (attr.get(ATTR_LATITUDE), attr.get(ATTR_LONGITUDE))
def _resolve_zone(self, friendly_name):
entities = self._hass.states.all()
for entity in entities:
if entity.domain == 'zone' and entity.name == friendly_name:
return self._get_location_from_attributes(entity)
return friendly_name