@petro Using your code as custom_component works like a charm. Thanks.
No problem!
Yes, Iām using your code too (and have been for a while) it is perfect, never had an issue with it so thanks from me too.
I donāt know how these things work so Iām speaking from a position of ignorance but it I donāt understand why the built-in version isnāt as good as the unofficial version!
Something in the component doesnāt meet HA standards Iām guessing.
Itās failing 1/9 build checks. Not sure how to fix it.
On the pull request on github it looks like someone was working on it too and offered to help. Would that help get this great addition into HA?
Yes, but another PR got added and now I need to account for those parameters. This might have been a first come first serve thing because I believe that PR was entered before mine. So the delay may have been due to that the whole time.
So here is the current rev. Iām going to submit another PR. This version has the current implementation using the include/exclude routes options.
EDIT: It also includes all suggestions thus far in the thread. I.E better sorting and so forth.
"""
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_DISTANCE = 'distance'
ATTR_ROUTE = 'route'
CONF_ATTRIBUTION = "Data provided by the Waze.com"
CONF_DESTINATION = 'destination'
CONF_ORIGIN = 'origin'
CONF_INCL_FILTER = 'incl_filter'
CONF_EXCL_FILTER = 'excl_filter'
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,
vol.Optional(CONF_INCL_FILTER): cv.string,
vol.Optional(CONF_EXCL_FILTER): 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)
incl_filter = config.get(CONF_INCL_FILTER)
excl_filter = config.get(CONF_EXCL_FILTER)
sensor = WazeTravelTime(hass, name, origin, destination, region, incl_filter, excl_filter)
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, incl_filter, excl_filter):
"""Initialize the Waze travel time sensor."""
self._hass = hass
self._name = name
self._region = region
self._incl_filter = incl_filter
self._excl_filter = excl_filter
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."""
res = {ATTR_ATTRIBUTION:CONF_ATTRIBUTION}
if 'duration' in self._state:
res['duration'] = self._state['duration']
if 'distance' in self._state:
res['distance'] = self._state['distance']
if 'route' in self._state:
res['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):
"""Get a lat/long from a zones 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()
if self._incl_filter is not None:
routes = {k: v for k, v in routes.items() if
self._incl_filter.lower() in k.lower()}
if self._excl_filter is not None:
routes = {k: v for k, v in routes.items() if
self._excl_filter.lower() not in k.lower()}
route = sorted(routes,key=(lambda key: routes[key][0]))[0]
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
@pbavinck, @klogg, @Timelike, @rodon, @Amir974, @masterkenobi, @svaguilarv, @PostSven
PR got merged this week, expect this in the next build with all updates contained in this thread.
Did this happen? I just got back from 2 weeks away, upgraded to 0.72.0 and removed the custom component but it seems it didnāt.
Or have I missed something stupid?
The process for PRās is new to me. The changes got merged into patch-5. I do not know how long it takes patch-5 to get into a official release.
Actually, it will be in 0.73. The process takes a bit. So it got merged when 0.72 was in beta. So it is now in the beta and should be released next. Soā¦ 2 weeks
Thats great! Thanks
Hi @petro
with the following config:
- platform: waze_travel_time
name: "Amir to work"
origin: device_tracker.myphone
destination: zone.work
region: 'IL'
My log is getting full with the following errors since upgrading,
Traceback (most recent call last):
File "/usr/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 197, in async_update_ha_state
yield from self.async_device_update()
File "/usr/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 320, 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/util/__init__.py", line 319, in wrapper
result = method(*args, **kwargs)
File "/config/custom_components/sensor/waze_travel_time.py", line 204, 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)
This config used to work with your previous version (from a few weeks agoā¦)
Please let me know what I can do about it, hopefully asap - before the next version / PR
thanks!
I have that exact same error message with the āstandardā component.
@eifinger, @Amir974, the error message is coming from the resource library. I donāt actually have control over that library and the library hasnāt been updated in years. Iām not entirely sure why the original waze_travel_time component used it. Itās something that I donāt think I have the ability to change. Itās in my log too. I may be able to catch the error and suppress it, but at this point we need to wait til itās in the release before I can make another PR.
EDIT: Iāll look into it deeper. Maybe one of the inputās is incorrectly formatted. If thatās not the case, then iāll catch the error and suppress it.
EDIT: I think it occurs when one of the trackers/location is bad.
I thought there is a fast track for patch in case a component is loosing basic functionality like that -the sensor is not producing data since 0.72ā¦
Any way if you figure this out and can suggest a fix in custom_component folder or something like that - it would be awesome!
Edit:
BTW - this is still happening after I revert to my 0.71 backup, using the exact same code you provided earlier on - to the custom_componentā¦
is it possible an update was made to a utility you are referencing or something else that got pulled in the past couple of days thatās causing it to fail ?
The backup version I restored too worked perfectlyā¦
Edit:
P.S itās happening with same tracker and zones - both seem fine with normal lon/lat showing in dev tools - states
Could it be that the waze api changed?
Hi,
Actually, the WazeRouteCalculator is giving back a 403 error. Thatās why the json decoder is returning an error in HA.
So youāre right, itās probably due to some changes in Waze API.
Iām trying to solve that problem but Iām not sure if this is a temporary issue or not on Waze side.
no update was made to the utilty that the waze app is referencing. Itās been the same old reference for 2+ years without updates. This issue has been in there since day 1, but there may be ways to avoid it.
That is possible