Waze travel time update


#21

No, but i’ll start working on it. I was hoping it would make the update before I made changes. Looks like that won’t happen. I’ll update over the next few days


#22

Hi there’s, any news. :slight_smile:

Plus - a question:

You said polling happens every 5 minutes, but in the DB I see an update saved every 10 minutes - any idea why?

How can I control the time between polls in the config / code?

Thanks!


#23

Yes, polling happens about every 5 minutes. If you want to change the interval, update this line of code:

SCAN_INTERVAL = timedelta(minutes=5)

It’s towards the top of the file.

I’ll let you know when I have an update. Haven’t had a chance to look at it yet.


#24

I’m wondering does this component supports other countries such as those in Asia?


#25

It does not. I’ve been looking for a list of regions that the API supports but I cannot find one. I’m pretty sure that waze app on phones works in Asia. Once I find the region list, I can attempt to add support for the other area’s to the WazeRouteCalculator.py files.


#26

Give this a whirl and let me know if it updates immediately. The interval is still 5 minutes, i’m trying to have it match the google component.

"""
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 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

        self._update()

    @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 "{},{}".format(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):
        self._update()

    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

#27

I have 4 Waze sensors setup

with the new code I see one updating immediately after HA launch complete (random - not the same sensor on each load) ** for the rest I see 3 * the following error in the log** (one per sensor):

2018-05-04 09:15:57 ERROR (MainThread) [homeassistant.core] Error doing job: Future exception was never retrieved
Traceback (most recent call last):
  File "/usr/lib/python3.6/concurrent/futures/thread.py", line 56, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/config/custom_components/sensor/waze_travel_time.py", line 59, in run_setup
    sensor = WazeTravelTime(hass, name, origin, destination, region)
  File "/config/custom_components/sensor/waze_travel_time.py", line 85, in __init__
    self._update()
  File "/config/custom_components/sensor/waze_travel_time.py", line 194, in _update
    routes = params.calc_all_routes_info()
  File "/usr/lib/python3.6/site-packages/WazeRouteCalculator/WazeRouteCalculator.py", line 142, in calc_all_routes_info
    routes = self.get_route(npaths, time_delta)
  File "/usr/lib/python3.6/site-packages/WazeRouteCalculator/WazeRouteCalculator.py", line 92, in get_route
    response_json = response.json()
  File "/usr/lib/python3.6/site-packages/requests/models.py", line 892, in json
    return complexjson.loads(self.text, **kwargs)
  File "/usr/lib/python3.6/site-packages/simplejson/__init__.py", line 518, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.6/site-packages/simplejson/decoder.py", line 370, in decode
    obj, end = self.raw_decode(s)
  File "/usr/lib/python3.6/site-packages/simplejson/decoder.py", line 400, in raw_decode
    return self.scan_once(s, idx=_w(s, idx).end())
simplejson.errors.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

#28

Okay. That’s odd because I didn’t change anything related to the calculator. I’ll have to see what the initial update is doing because it seems like that is causing the problem. Maybe during startup the devices aren’t initialized yet. I’ll take a look tonight.


#29

what 4 waze sensors do you have setup? I’ve been running this with a 1 minute update (and it loads on startup) without errors for 3 days using 4 sensors: lat/long, device_trackers, zones, and a sensor. Can you link your configuration so I can mimic it?


#30

Nothing special really…

sensor:
  - platform: waze_travel_time
    name: "iphone to work"
    origin: device_tracker.iphone
    destination: zone.work
    region: 'IL'
  - platform: waze_travel_time
    name: "iphone to home"
    origin: device_tracker.iphone
    destination: zone.home
    region: 'IL'
 #
  - platform: waze_travel_time
    name: "Home to work"
    origin: zone.home
    destination: zone.work
    region: 'IL'
  - platform: waze_travel_time
    name: "Work to home"
    origin: zone.work
    destination: zone.home
    region: 'IL'

#31

I’m currently using this Waze component in Hass.io. Would it be possible to get the “avoid toll roads” option as a future configuration variable? Thanks!


#32

I haven’t tried this specific version of the code, but for my purpose there is still an issue with this code. If i use this version it is not defined which route is selected, and for my routes specifically it’s neither the fastest nor the shortest.

I’m unable to make a full pull request but i suggest this change to make it the fastest route by default. This means replacing:
route = next(iter(routes))
by:
route=sorted(routes,key=(lambda key: routes[key][0]))[0]

I’ve tried this in a slightly different version of the code, it gives the fastest route by default. The reason I’ve put this in the forum is that it’s nowhere written the fastest route is selected.


#33

The Waze API claims it’s orders them via the fastest route. I’ve never verified that. Either way, implementing that line won’t be hard. The PR still hasn’t gone through to get into the official build so for the time being, just use your own custom component. I’m going to try to see why the PR isn’t going through and I can ninja this fix in.


#34

Thanx for the reply.
I cannot verify the statement regarding the ordering but the used python lib (WazeRouteCalculator) provides a standard dict, which to my knowledge is not sorted.

I’d happily use my own component until these changes make there way in the official build. Until then keep up the good work.


#35

I see this is included now in 0.70 but the docs don’t mention that the origin and destination can be a zone or a device tracker. Does the included component work in exactly the same way as the custom one?


#36

I don’t believe it’s included yet.


#37

Actually, a different PR was included that included or excluded devices. I’ll probably have to make changes to the PR to handle the new additions.


#38

Using 0.69.1 & 0.70, I get the following error on using the waze travel time component:

waze_travel_time: Error on device update!
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/homeassistant/helpers/entity_platform.py", line 244, in _async_add_entity
    await entity.async_device_update(warning=False)
  File "/usr/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 327, in async_device_update
    yield from self.hass.async_add_job(self.update)
  File "/usr/lib/python3.6/concurrent/futures/thread.py", line 56, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/lib/python3.6/site-packages/homeassistant/components/sensor/waze_travel_time.py", line 110, in update
    self.waze_data.update()
  File "/usr/lib/python3.6/site-packages/homeassistant/util/__init__.py", line 319, in wrapper
    result = method(*args, **kwargs)
  File "/usr/lib/python3.6/site-packages/homeassistant/components/sensor/waze_travel_time.py", line 135, in update
    self._origin, self._destination, self._region, None)
  File "/usr/lib/python3.6/site-packages/WazeRouteCalculator/WazeRouteCalculator.py", line 35, in __init__
    self.start_coords = self.address_to_coords(start_address)
  File "/usr/lib/python3.6/site-packages/WazeRouteCalculator/WazeRouteCalculator.py", line 58, in address_to_coords
    response_json = response.json()[0]
IndexError: list index out of range

My config looks like:

- platform: waze_travel_time
  name: "Peter To Home (Waze)"
  origin: device_tracker.phone_pixel
  destination: zone.home
  region: 'EU'

I tried the waze component with hard coded addresses and that worked just fine.
Any ideas how to fix?


#39

Are you using the code pasted above or what is installed with HA? The normal installation doesn’t currently support entity_id’s. I’m not sure why the PR is taking so long to get into a normal build.


#40

Ah ok, am using the standard install. Thanks for clarifying!