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
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
import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.location as location
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
@ -65,6 +69,8 @@ PLATFORM_SCHEMA = vol.Schema({
}))
})
TRACKABLE_DOMAINS = ["device_tracker", "sensor", "zone"]
def convert_time_to_utc(timestr):
"""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):
"""Setup the travel time platform."""
# 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:
if hass.config.temperature_unit is TEMP_CELSIUS:
options['units'] = 'metric'
elif hass.config.temperature_unit is TEMP_FAHRENHEIT:
options['units'] = 'imperial'
This allows any entities to be created already
"""
options = config.get(CONF_OPTIONS)
travel_mode = config.get(CONF_TRAVEL_MODE)
mode = options.get(CONF_MODE)
if options.get('units') is None:
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:
wstr = ("Google Travel Time: travel_mode is deprecated, please add "
"mode to the options dictionary instead!")
_LOGGER.warning(wstr)
if mode is None:
options[CONF_MODE] = travel_mode
travel_mode = config.get(CONF_TRAVEL_MODE)
mode = options.get(CONF_MODE)
titled_mode = options.get(CONF_MODE).title()
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 travel_mode is not None:
wstr = ("Google Travel Time: travel_mode is deprecated, please "
"add mode to the options dictionary instead!")
_LOGGER.warning(wstr)
if mode is None:
options[CONF_MODE] = travel_mode
sensor = GoogleTravelTimeSensor(name, api_key, origin, destination,
options)
titled_mode = options.get(CONF_MODE).title()
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:
add_devices_callback([sensor])
sensor = GoogleTravelTimeSensor(hass, name, api_key, origin,
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
@ -115,15 +129,25 @@ class GoogleTravelTimeSensor(Entity):
"""Representation of a tavel time sensor."""
# 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."""
self._hass = hass
self._name = name
self._options = options
self._origin = origin
self._destination = destination
self._matrix = None
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
self._client = googlemaps.Client(api_key, timeout=10)
try:
@ -136,6 +160,9 @@ class GoogleTravelTimeSensor(Entity):
@property
def state(self):
"""Return the state of the sensor."""
if self._matrix is None:
return None
_data = self._matrix['rows'][0]['elements'][0]
if 'duration_in_traffic' in _data:
return round(_data['duration_in_traffic']['value']/60)
@ -151,6 +178,9 @@ class GoogleTravelTimeSensor(Entity):
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self._matrix is None:
return None
res = self._matrix.copy()
res.update(self._options)
del res['rows']
@ -186,6 +216,64 @@ class GoogleTravelTimeSensor(Entity):
elif atime is not None:
options_copy['arrival_time'] = atime
self._matrix = self._client.distance_matrix(self._origin,
self._destination,
**options_copy)
# Convert device_trackers to google friendly location
if hasattr(self, '_origin_entity_id'):
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