Waze travel time update


#1

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

Waze Travel Time [origin attribute] not accepting value / template?
Waze Travel Time - error
#2

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


#3

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.


#4

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


#5

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.


#6

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


#7

Am I right in thinking this update is not ‘live’ in the component yet and as such is not available for Hass.io users?


#8

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


#9

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


#10

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.


#11

Ah, ok thank you!


#12

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.


#13

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.


#14

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

Thanks,


#15

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.


Get GPS from Address
#16

Hi, what region should I set if I’m from Chile / South America?

Or is it not supported? :sweat:


#17

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.


#18

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.


#19

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.


#20

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.