Waze travel time update

Hey guys,

I updated the waze travel time sensor to handle device trackers, sensors, and zones. Take a look. Feedback is welcome.

"""
Support for Waze travel time sensor.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.waze_travel_time/
"""
from datetime import timedelta
import logging

import requests
import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, CONF_REGION, EVENT_HOMEASSISTANT_START, ATTR_LATITUDE, ATTR_LONGITUDE
import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.location as location
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle

REQUIREMENTS = ['WazeRouteCalculator==0.5']

_LOGGER = logging.getLogger(__name__)

ATTR_DURATION = 'duration'
ATTR_DISTANCE = 'distance'
ATTR_ROUTE = 'route'

CONF_ATTRIBUTION = "Data provided by the Waze.com"
CONF_DESTINATION = 'destination'
CONF_ORIGIN = 'origin'

DEFAULT_NAME = 'Waze Travel Time'

ICON = 'mdi:car'

REGIONS = ['US', 'NA', 'EU', 'IL']

SCAN_INTERVAL = timedelta(minutes=5)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    vol.Required(CONF_ORIGIN): cv.string,
    vol.Required(CONF_DESTINATION): cv.string,
    vol.Required(CONF_REGION): vol.In(REGIONS),
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})

TRACKABLE_DOMAINS = ['device_tracker', 'sensor', 'zone']

def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the Waze travel time sensor platform."""
    def run_setup(event):
        destination = config.get(CONF_DESTINATION)
        name = config.get(CONF_NAME)
        origin = config.get(CONF_ORIGIN)
        region = config.get(CONF_REGION)
        
        sensor = WazeTravelTime(hass, name, origin, destination, region)
        add_devices([sensor])
        
    hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup)

class WazeTravelTime(Entity):
    """Representation of a Waze travel time sensor."""

    def __init__(self, hass, name, origin, destination, region):
        """Initialize the Waze travel time sensor."""
        self._hass = hass
        self._name = name
        self._region = region
        self._state = None
        
        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

    @property
    def name(self):
        """Return the name of the sensor."""
        return self._name

    @property
    def state(self):
        """Return the state of the sensor."""
        if self._state is None:
            return None
            
        if 'duration' in self._state:
            return round(self._state['duration'])
        return None

    @property
    def unit_of_measurement(self):
        """Return the unit of measurement."""
        return 'min'

    @property
    def icon(self):
        """Icon to use in the frontend, if any."""
        return ICON

    @property
    def device_state_attributes(self):
        """Return the state attributes of the last update."""
        if self._state is None:
            return None
        
        res = {ATTR_ATTRIBUTION:CONF_ATTRIBUTION}
        if 'duration' in self._state:
            res[ATTR_DURATION] = self._state['duration']
        if 'distance' in self._state:
            res[ATTR_DISTANCE] = self._state['distance']
        if 'route' in self._state:
            res[ATTR_ROUTE] = self._state['route']            
        return res
        
    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)
            return None

        # Check if the entity has location attributes (zone)
        if location.has_location(entity):
            return self._get_location_from_attributes(entity)

        # Check if device is in a zone (device_tracker)
        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

        # 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
        
    @Throttle(SCAN_INTERVAL)
    def update(self):
        """Fetch new state data for the sensor."""
        import WazeRouteCalculator
        
        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:
            try:
                params = WazeRouteCalculator.WazeRouteCalculator(
                    self._origin, self._destination, self._region)
                routes = params.calc_all_routes_info()
                route = next(iter(routes))
                duration, distance = routes[route]
                route = bytes(route, 'ISO-8859-1').decode('UTF-8')
                self._state = {
                    'duration': duration,
                    'distance': distance,
                    'route': route}
            except WazeRouteCalculator.WRCError as exp:
                _LOGGER.error("Error on retrieving data: %s", exp)
                return
            except KeyError:
                _LOGGER.error("Error retrieving data from server")
                return
4 Likes

Thanks @petro

So, the good news the configuration accepts values in the origin & destination in various combinations.
However, for some reason it takes a little over 5 minutes for data to come in (while the output is ā€œunknownā€ at first.

I checked with the official code and it loads output as soon as the HA is loaded (same as google time travel),
I also verified with the official ā€œwaze travel timeā€ code in the custom components folder that it loads immediately on launch (so itā€™s not a custom component delay of some sort).

I am guessing something in the fix is causing a delay in data showing up in HA.
if you add some debug notifications I may be able to give you more dataā€¦

Other then that - great jobā€¦ I am keeping the fix on, delays and all - much more functional for me so thanks !

(and let me know if you have any update please!)

Yeah, It may be how I handled the updates. Iā€™ll take a look tonight. The polling time is 5 minutes. I probably just donā€™t poll on startup.

1 Like

Component seems to work fine with the ā€œdevice_trackerā€ and ā€œzone.ā€ configurations, i am getting a lot of errors in my event log: Update of sensor.mysensor is taking over 10 seconds

can you post the errors? This uses WazeRouteCalculator, which was not written by me. Not sure how it behaves with multiple uses throughout the day.

The error/warning is not very helpful but recurring every 10 minutes.

2018-04-23 09:35:07 WARNING (MainThread) [homeassistant.helpers.entity] Update of sensor.joyce_to_home is taking over 10 seconds 2018-04-23 09:35:20 WARNING (MainThread) [homeassistant.helpers.entity] Update of sensor.sven_to_home is taking over 10 seconds

Am I right in thinking this update is not ā€˜liveā€™ in the component yet and as such is not available for Hass.io users?

It is available as a custom_component, you can use that in hass.io if you want.

I meant the usage of zones and device trackers. I couldnā€™t get that bit to work but if you meant that that is also available then I will look again at my code. (The docs havenā€™t been updated on the website to include these improvements so I wondered if they had been ā€˜officiallyā€™ released).

i mean that you can use the code posted above as a custom_component, it has not been merged into the official release yet.

Petroā€™s reply in a different topic related to this:

if you place it in hass_loc/custom_components/sensor/ it will override it.

Heres a link to the source. Paste the code into a file named waze_travel_time.py in the directory above. Hereā€™s a link to the code:

Hey guys, I updated the waze travel time sensor to handle device trackers, sensors, and zones. Take a look. Feedback is welcome. """ Support for Waze travel time sensor. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.waze_travel_time/ """ from datetime import timedelta import logging import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ATTā€¦ 

I made a PR, so it should get into the regular HA build in the next release.

Ah, ok thank you!

1 Like

Perfect! thanks for the help and thanks too to @petro for doing this.

It all seems to work well (after the first few minutes at least!) except that I can confirm that it doesnā€™t seem to update when it first starts.

Iā€™ll report back if I have any further feedback but thanks again.

Yeah, its not officially released. Itā€™s sitting in the PR requests so itā€™s only a matter of time. Once that PR goes through, iā€™ll update it to report on startup.

This can be ignored, wazeroutecalculator does not like Pi-Hole.

Thanks,

Iā€™m not a fan of wazeroutecalculator, but itā€™s what the original component writer used. Thereā€™s little to no configuration in that repo.

Hi, what region should I set if Iā€™m from Chile / South America?

Or is it not supported? :sweat:

Have you tried NA? I didnā€™t write the WazeRouteCalculator which is a resource of this component. It looks like it only supports 3 regions; US, IL, and EU. The guy who made WazeRouteCalculator added NA which under the hood just flips it to US. US is basically NA. I may be able to update that calculator, but I donā€™t have control over it. Iā€™ll see what can be done, but for the time being, try using NA or US. Let me know if that works.

Thanks for your reply.

I just tried that but I get the following error:
image

I tried it placing the GPS coordinates in origin and destination and i tried also putting the address of each one too. But I get the same error.

Iā€™ll have to research it. I know exactly where to change it in the script. I was unable to find the correct code to supply the waze api for south america. They have to have it, I just need to find it. Iā€™ll have some time next week to look around, but I may have to contact waze customer support for the info.

1 Like

Seems like this PR did not make it to 0.68 :frowning:

Do you have an update since the original that did not load on startup @petro ?

Please add a link if thatā€™s something you recommend until the official waze sensor getā€™s this feature.