Waze travel time update

Doesn’t look like the fix made it in 0.88.0

I ended up for now just modifying the wazeroutecalculator.py changef line 130 to this

        route_distance = (distance / 1000) * 0.621371

It’s hackish, and will be overwritten on the next update, but for now it’s working.

make it a custom component with that change and it won’t be overwritten. I plan on going through the component sometime soon. Trying to set up a proper dev environment. Hasn’t been going great.

1 Like

Quick question, How would you make this a custom component under the new naming convention? wazeroutecalculator.py is a dependency of the waze_travel_time.py under sensors, right? I dont really fully understand the file and system structure quite yet. I kind of get the sensors and all the .py under there,but not as much when it comes to dependencies.

here are the upcoming changes to the component

view the file in raw format. Copy the contents.

Go to your config folder. Create the following path

custom_components\sensor

inside that folder create a file named waze_travel_time.py

paste the raw contents into that file.

The component now pays attention to your system units and converts the numbers over (imperial = miles, metric = km). It also turns that stupid reoccurring error into a warning. So now you can suppress the warning.

Looks like the following fix was added to 0.89.0, but after upgrading, I get the errors below.

Fix "Unable to find entity" at Waze component ([@VirtualL](https://github.com/VirtualL) - [#21087](https://github.com/home-assistant/home-assistant/pull/21087)) ([sensor.waze_travel_time docs](https://www.home-assistant.io/components/sensor.waze_travel_time/))

2019-03-07 08:40:16 ERROR (Thread-4) [homeassistant.components.sensor.waze_travel_time] Error on retrieving data: empty response
2019-03-07 08:40:16 ERROR (Thread-12) [homeassistant.components.sensor.waze_travel_time] Error on retrieving data: empty response

Yes this means waze api didn’t respond. This is unavoidable as I have no control over the waze api returning errors. The next build will switch that to a warning.

Awesome, thanks.

hopefully it is just a waze api issue, but today i upgraded to 89 and also instantly got that error as above

The waze api that the component uses is an unofficial API. The likelihood of this getting better is low.

Ok after further investigation, it appears as if there is an issue in WazeRouteCalculator. A pr has been created. If anyone has the time, please check out the PR on WazeRouteCalculator. It should fix all these empty response errors in the logs.

Disclaimer

This error is OUTSIDE HA. I have no control over the fix. Please comment on the PR, even test out the PR if you can. I have no affiliation with WazeRouteCalculator, hopefully the dev will accept my PR. It fixed issues in 11 sensors that I use. I went from 700/1320+ plus errors per hour down to zero 4/1320 per hour. This was with a polling of twice a minute. In theory it should reduce issues to zero over a 24 hour period polling every 5 minutes (Normal settings in HA).

@ba5eline, @Coolie1101, @ptdalen, @Mariusthvdb

HI and thanks for your efforts on this.
I’d be gad to test and try, to see what happens.
Can we add this as a custom_component? If so, how exactly.

If you use the following code as /config/custom_components/waze_travel_time/sensor.py, this will test and execute the PR as well as use the new configuration for waze.

disclaimer: This should only be used testing. Do not use it as your custom 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 re

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, CONF_UNIT_SYSTEM_METRIC,
    CONF_UNIT_SYSTEM_IMPERIAL)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import location
from homeassistant.helpers.entity import Entity

REQUIREMENTS = ['https://github.com/kovacsbalu/WazeRouteCalculator/archive/04e9e9485ce61465dec6bebff1d9e987154abe6b.zip#WazeRouteCalculator==0.9.1b0']

_LOGGER = logging.getLogger(__name__)

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

ATTRIBUTION = "Powered by Waze"

CONF_DESTINATION = 'destination'
CONF_ORIGIN = 'origin'
CONF_INCL_FILTER = 'incl_filter'
CONF_EXCL_FILTER = 'excl_filter'
CONF_REALTIME = 'realtime'
CONF_UNITS = 'units'
CONF_VEHICLE_TYPE = 'vehicle_type'

DEFAULT_NAME = 'Waze Travel Time'
DEFAULT_REALTIME = False
DEFAULT_VEHICLE_TYPE = 'car'

ICON = 'mdi:car'

UNITS = [CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL]

REGIONS = ['US', 'NA', 'EU', 'IL', 'AU']
VEHICLE_TYPES = ['car', 'taxi', 'motorcycle']

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,
    vol.Optional(CONF_INCL_FILTER): cv.string,
    vol.Optional(CONF_EXCL_FILTER): cv.string,
    vol.Optional(CONF_REALTIME, default=DEFAULT_REALTIME): cv.boolean,
    vol.Optional(CONF_VEHICLE_TYPE,
                 default=DEFAULT_VEHICLE_TYPE): vol.In(VEHICLE_TYPES),
    vol.Optional(CONF_UNITS): vol.In(UNITS)
})


def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Waze travel time sensor platform."""
    destination = config.get(CONF_DESTINATION)
    name = config.get(CONF_NAME)
    origin = config.get(CONF_ORIGIN)
    region = config.get(CONF_REGION)
    incl_filter = config.get(CONF_INCL_FILTER)
    excl_filter = config.get(CONF_EXCL_FILTER)
    realtime = config.get(CONF_REALTIME)
    vehicle_type = config.get(CONF_VEHICLE_TYPE)
    units = config.get(CONF_UNITS)

    if units is None:
        units = hass.config.units.name

    data = WazeTravelTimeData(None, None, region, incl_filter,
                              excl_filter, realtime, units,
                              vehicle_type)

    sensor = WazeTravelTime(name, origin, destination, data)

    add_entities([sensor])

    # Wait until start event is sent to load this component.
    hass.bus.listen_once(
        EVENT_HOMEASSISTANT_START, lambda _: sensor.update())


def _get_location_from_attributes(state):
    """Get the lat/long string from an states attributes."""
    attr = state.attributes
    return '{},{}'.format(attr.get(ATTR_LATITUDE), attr.get(ATTR_LONGITUDE))


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

    def __init__(self, name, origin, destination, waze_data):
        """Initialize the Waze travel time sensor."""
        self._name = name
        self._waze_data = waze_data
        self._state = None
        self._origin_entity_id = None
        self._destination_entity_id = None

        # Attempt to find entity_id without finding address with period.
        pattern = "(?<![a-zA-Z0-9 ])[a-z_]+[.][a-zA-Z0-9_]+"

        if re.fullmatch(pattern, origin):
            _LOGGER.debug("Found origin source entity %s", origin)
            self._origin_entity_id = origin
        else:
            self._waze_data.origin = origin

        if re.fullmatch(pattern, destination):
            _LOGGER.debug("Found destination source entity %s", destination)
            self._destination_entity_id = destination
        else:
            self._waze_data.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._waze_data.duration is not None:
            return round(self._waze_data.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._waze_data.duration is None:
            return None

        res = {ATTR_ATTRIBUTION: ATTRIBUTION}
        res[ATTR_DURATION] = self._waze_data.duration
        res[ATTR_DISTANCE] = self._waze_data.distance
        res[ATTR_ROUTE] = self._waze_data.route
        return res

    def _get_location_from_entity(self, entity_id):
        """Get the location from the entity_id."""
        state = self.hass.states.get(entity_id)

        if state is None:
            _LOGGER.error("Unable to find entity %s", entity_id)
            return None

        # Check if the entity has location attributes.
        if location.has_location(state):
            _LOGGER.debug("Getting %s location", entity_id)
            return _get_location_from_attributes(state)

        # Check if device is inside a zone.
        zone_state = self.hass.states.get('zone.{}'.format(state.state))
        if location.has_location(zone_state):
            _LOGGER.debug(
                "%s is in %s, getting zone location",
                entity_id, zone_state.entity_id
            )
            return _get_location_from_attributes(zone_state)

        # If zone was not found in state then use the state as the location.
        if entity_id.startswith('sensor.'):
            return state.state

        # When everything fails just return nothing.
        return None

    def _resolve_zone(self, friendly_name):
        """Get a lat/long from a zones friendly_name."""
        states = self.hass.states.all()
        for state in states:
            if state.domain == 'zone' and state.name == friendly_name:
                return _get_location_from_attributes(state)

        return friendly_name

    def update(self):
        """Fetch new state data for the sensor."""
        _LOGGER.debug("Fetching Route for %s", self._name)
        # Get origin latitude and longitude from entity_id.
        if self._origin_entity_id is not None:
            self._waze_data.origin = self._get_location_from_entity(
                self._origin_entity_id)

        # Get destination latitude and longitude from entity_id.
        if self._destination_entity_id is not None:
            self._waze_data.destination = self._get_location_from_entity(
                self._destination_entity_id)

        # Get origin from zone name.
        self._waze_data.origin = self._resolve_zone(
            self._waze_data.origin)

        # Get desination from zone name.
        self._waze_data.destination = self._resolve_zone(
            self._waze_data.destination)

        self._waze_data.update()


class WazeTravelTimeData():
    """WazeTravelTime Data object."""

    def __init__(self, origin, destination, region, include, exclude,
                 realtime, units, vehicle_type):
        """Set up WazeRouteCalculator."""
        import WazeRouteCalculator

        self._calc = WazeRouteCalculator

        self.origin = origin
        self.destination = destination
        self.region = region
        self.include = include
        self.exclude = exclude
        self.realtime = realtime
        self.units = units
        self.duration = None
        self.distance = None
        self.route = None

        # Currently WazeRouteCalc only supports PRIVATE, TAXI, MOTORCYCLE.
        if vehicle_type.upper() == 'CAR':
            # Empty means PRIVATE for waze which translates to car.
            self.vehicle_type = ''
        else:
            self.vehicle_type = vehicle_type.upper()

    def update(self):
        """Update WazeRouteCalculator Sensor."""
        if self.origin is not None and self.destination is not None:
            try:
                params = self._calc.WazeRouteCalculator(
                    self.origin, self.destination, self.region,
                    self.vehicle_type, log_lvl=logging.DEBUG)
                routes = params.calc_all_routes_info(real_time=self.realtime)

                if self.include is not None:
                    routes = {k: v for k, v in routes.items() if
                              self.include.lower() in k.lower()}

                if self.exclude is not None:
                    routes = {k: v for k, v in routes.items() if
                              self.exclude.lower() in k.lower()}

                route = sorted(routes, key=(lambda key: routes[key][0]))[0]

                self.duration, distance = routes[route]

                if self.units == CONF_UNIT_SYSTEM_IMPERIAL:
                    # Convert to miles.
                    self.distance = distance / 1.609
                else:
                    self.distance = distance

                self.route = route
            except self._calc.WRCError as exp:
                _LOGGER.warning("Error on retrieving data: %s", exp)
                return
            except KeyError:
                _LOGGER.error("Error retrieving data from server")
                return
1 Like

thanks. I am still on 84.3 here though, so I should be using the previous settings for custom_components… I think that would imply I have to use this as waze_travel_time.py, in /config/custom_components/sensor/ ?
Or wouldn’t that be possible, because of specifics in the sensor.py file.

Yes,

/config/custom_components/sensor/waze_travel_time.py

can report: not a single startup error left.

Have a feeling the system has some lag now though, and many of my waze sensors were rather late in the frontend after startup. They are all there now, so maybe the new sensor had to be initialized somehow? we’ll see what happens.

There hasn’t been a fix for the startup warnings. Only the empty response during run. I’m not entirely convinced that the test I posted is using the PR version yet.

On my dev version, this is my current logs for the past 2 hours:

2019-03-10 11:14:26 WARNING (SyncWorker_12) [homeassistant.components.sensor.waze_travel_time] Error on retrieving data: Empty Response
2019-03-10 11:20:30 WARNING (SyncWorker_18) [homeassistant.components.sensor.waze_travel_time] Error on retrieving data: Empty Response
2019-03-10 11:20:30 WARNING (SyncWorker_0) [homeassistant.components.sensor.waze_travel_time] Error on retrieving data: Empty Response
2019-03-10 11:21:31 WARNING (SyncWorker_2) [homeassistant.components.sensor.waze_travel_time] Error on retrieving data: Empty Response

and these errors coincide with me attempting to use the updates in my non-dev home-asstant that runs house.

well what can I say, Ive always had many errors during startup an now see this:

which means I don’t have errors at startup, and no errors during runtime.

Empty response is unknown to me, I have never seen that (I think)

Interesting. Well, don’t bother using this then. They are frequent in new versions.

deleted your CC and the errors are back again:

giving it another shot now to see if I tested correctly

update

can confirm again: using the testing CC, all my startup errors are gone. Not a single waze reference in the log. Note: I havent set logging to debug, if that might be necessary for your Empty repose error, id have to change that and start once more?