mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 22:27:07 +00:00
Refactor Waze Travel Time & Update Requirements (#22428)
* Refactor Waze Travel Time & Update Requirements Refactored Waze Travel Time to contain a data object. Changed error retrieving data to a warning. Added distance conversion depending on region. Removed dependency on TRACKABLE_DOMAINS list. Update to use WazeRouteCalculator 0.10 3rd time's a charm. Deleted fork, caused last PR to screw up. So here we are. * Update requirements_all.txt * Revert package upgrade. * Revert package upgrade.
This commit is contained in:
parent
1c1363875c
commit
2c341f2a65
@ -1,17 +1,18 @@
|
|||||||
"""Support for Waze travel time sensor."""
|
"""Support for Waze travel time sensor."""
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION, CONF_NAME, CONF_REGION, EVENT_HOMEASSISTANT_START,
|
ATTR_ATTRIBUTION, CONF_NAME, CONF_REGION, EVENT_HOMEASSISTANT_START,
|
||||||
ATTR_LATITUDE, ATTR_LONGITUDE)
|
ATTR_LATITUDE, ATTR_LONGITUDE, CONF_UNIT_SYSTEM_METRIC,
|
||||||
|
CONF_UNIT_SYSTEM_IMPERIAL)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers import location
|
from homeassistant.helpers import location
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.util import Throttle
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -26,18 +27,21 @@ CONF_ORIGIN = 'origin'
|
|||||||
CONF_INCL_FILTER = 'incl_filter'
|
CONF_INCL_FILTER = 'incl_filter'
|
||||||
CONF_EXCL_FILTER = 'excl_filter'
|
CONF_EXCL_FILTER = 'excl_filter'
|
||||||
CONF_REALTIME = 'realtime'
|
CONF_REALTIME = 'realtime'
|
||||||
|
CONF_UNITS = 'units'
|
||||||
|
CONF_VEHICLE_TYPE = 'vehicle_type'
|
||||||
|
|
||||||
DEFAULT_NAME = 'Waze Travel Time'
|
DEFAULT_NAME = 'Waze Travel Time'
|
||||||
DEFAULT_REALTIME = True
|
DEFAULT_REALTIME = True
|
||||||
|
DEFAULT_VEHICLE_TYPE = 'car'
|
||||||
|
|
||||||
ICON = 'mdi:car'
|
ICON = 'mdi:car'
|
||||||
|
|
||||||
|
UNITS = [CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL]
|
||||||
|
|
||||||
REGIONS = ['US', 'NA', 'EU', 'IL', 'AU']
|
REGIONS = ['US', 'NA', 'EU', 'IL', 'AU']
|
||||||
|
VEHICLE_TYPES = ['car', 'taxi', 'motorcycle']
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(minutes=5)
|
SCAN_INTERVAL = timedelta(minutes=5)
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
|
|
||||||
|
|
||||||
TRACKABLE_DOMAINS = ['device_tracker', 'sensor', 'zone', 'person']
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_ORIGIN): cv.string,
|
vol.Required(CONF_ORIGIN): cv.string,
|
||||||
@ -47,6 +51,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
vol.Optional(CONF_INCL_FILTER): cv.string,
|
vol.Optional(CONF_INCL_FILTER): cv.string,
|
||||||
vol.Optional(CONF_EXCL_FILTER): cv.string,
|
vol.Optional(CONF_EXCL_FILTER): cv.string,
|
||||||
vol.Optional(CONF_REALTIME, default=DEFAULT_REALTIME): cv.boolean,
|
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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -59,9 +66,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
incl_filter = config.get(CONF_INCL_FILTER)
|
incl_filter = config.get(CONF_INCL_FILTER)
|
||||||
excl_filter = config.get(CONF_EXCL_FILTER)
|
excl_filter = config.get(CONF_EXCL_FILTER)
|
||||||
realtime = config.get(CONF_REALTIME)
|
realtime = config.get(CONF_REALTIME)
|
||||||
|
vehicle_type = config.get(CONF_VEHICLE_TYPE)
|
||||||
|
units = config.get(CONF_UNITS, hass.config.units.name)
|
||||||
|
|
||||||
sensor = WazeTravelTime(name, origin, destination, region,
|
data = WazeTravelTimeData(None, None, region, incl_filter,
|
||||||
incl_filter, excl_filter, realtime)
|
excl_filter, realtime, units,
|
||||||
|
vehicle_type)
|
||||||
|
|
||||||
|
sensor = WazeTravelTime(name, origin, destination, data)
|
||||||
|
|
||||||
add_entities([sensor])
|
add_entities([sensor])
|
||||||
|
|
||||||
@ -79,27 +91,28 @@ def _get_location_from_attributes(state):
|
|||||||
class WazeTravelTime(Entity):
|
class WazeTravelTime(Entity):
|
||||||
"""Representation of a Waze travel time sensor."""
|
"""Representation of a Waze travel time sensor."""
|
||||||
|
|
||||||
def __init__(self, name, origin, destination, region,
|
def __init__(self, name, origin, destination, waze_data):
|
||||||
incl_filter, excl_filter, realtime):
|
|
||||||
"""Initialize the Waze travel time sensor."""
|
"""Initialize the Waze travel time sensor."""
|
||||||
self._name = name
|
self._name = name
|
||||||
self._region = region
|
self._waze_data = waze_data
|
||||||
self._incl_filter = incl_filter
|
|
||||||
self._excl_filter = excl_filter
|
|
||||||
self._realtime = realtime
|
|
||||||
self._state = None
|
self._state = None
|
||||||
self._origin_entity_id = None
|
self._origin_entity_id = None
|
||||||
self._destination_entity_id = None
|
self._destination_entity_id = None
|
||||||
|
|
||||||
if origin.split('.', 1)[0] in TRACKABLE_DOMAINS:
|
# 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
|
self._origin_entity_id = origin
|
||||||
else:
|
else:
|
||||||
self._origin = origin
|
self._waze_data.origin = origin
|
||||||
|
|
||||||
if destination.split('.', 1)[0] in TRACKABLE_DOMAINS:
|
if re.fullmatch(pattern, destination):
|
||||||
|
_LOGGER.debug("Found destination source entity %s", destination)
|
||||||
self._destination_entity_id = destination
|
self._destination_entity_id = destination
|
||||||
else:
|
else:
|
||||||
self._destination = destination
|
self._waze_data.destination = destination
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -109,11 +122,9 @@ class WazeTravelTime(Entity):
|
|||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
if self._state is None:
|
if self._waze_data.duration is not None:
|
||||||
return None
|
return round(self._waze_data.duration)
|
||||||
|
|
||||||
if 'duration' in self._state:
|
|
||||||
return round(self._state['duration'])
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -129,16 +140,13 @@ class WazeTravelTime(Entity):
|
|||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the state attributes of the last update."""
|
"""Return the state attributes of the last update."""
|
||||||
if self._state is None:
|
if self._waze_data.duration is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
res = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
res = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||||
if 'duration' in self._state:
|
res[ATTR_DURATION] = self._waze_data.duration
|
||||||
res[ATTR_DURATION] = self._state['duration']
|
res[ATTR_DISTANCE] = self._waze_data.distance
|
||||||
if 'distance' in self._state:
|
res[ATTR_ROUTE] = self._waze_data.route
|
||||||
res[ATTR_DISTANCE] = self._state['distance']
|
|
||||||
if 'route' in self._state:
|
|
||||||
res[ATTR_ROUTE] = self._state['route']
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _get_location_from_entity(self, entity_id):
|
def _get_location_from_entity(self, entity_id):
|
||||||
@ -149,11 +157,12 @@ class WazeTravelTime(Entity):
|
|||||||
_LOGGER.error("Unable to find entity %s", entity_id)
|
_LOGGER.error("Unable to find entity %s", entity_id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Check if the entity has location attributes (zone)
|
# Check if the entity has location attributes.
|
||||||
if location.has_location(state):
|
if location.has_location(state):
|
||||||
|
_LOGGER.debug("Getting %s location", entity_id)
|
||||||
return _get_location_from_attributes(state)
|
return _get_location_from_attributes(state)
|
||||||
|
|
||||||
# Check if device is in a zone (device_tracker)
|
# Check if device is inside a zone.
|
||||||
zone_state = self.hass.states.get('zone.{}'.format(state.state))
|
zone_state = self.hass.states.get('zone.{}'.format(state.state))
|
||||||
if location.has_location(zone_state):
|
if location.has_location(zone_state):
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
@ -162,11 +171,11 @@ class WazeTravelTime(Entity):
|
|||||||
)
|
)
|
||||||
return _get_location_from_attributes(zone_state)
|
return _get_location_from_attributes(zone_state)
|
||||||
|
|
||||||
# If zone was not found in state then use the state as the location
|
# If zone was not found in state then use the state as the location.
|
||||||
if entity_id.startswith('sensor.'):
|
if entity_id.startswith('sensor.'):
|
||||||
return state.state
|
return state.state
|
||||||
|
|
||||||
# When everything fails just return nothing
|
# When everything fails just return nothing.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _resolve_zone(self, friendly_name):
|
def _resolve_zone(self, friendly_name):
|
||||||
@ -178,46 +187,88 @@ class WazeTravelTime(Entity):
|
|||||||
|
|
||||||
return friendly_name
|
return friendly_name
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Fetch new state data for the sensor."""
|
"""Fetch new state data for the sensor."""
|
||||||
import WazeRouteCalculator
|
_LOGGER.debug("Fetching Route for %s", self._name)
|
||||||
|
# Get origin latitude and longitude from entity_id.
|
||||||
if self._origin_entity_id is not None:
|
if self._origin_entity_id is not None:
|
||||||
self._origin = self._get_location_from_entity(
|
self._waze_data.origin = self._get_location_from_entity(
|
||||||
self._origin_entity_id)
|
self._origin_entity_id)
|
||||||
|
|
||||||
|
# Get destination latitude and longitude from entity_id.
|
||||||
if self._destination_entity_id is not None:
|
if self._destination_entity_id is not None:
|
||||||
self._destination = self._get_location_from_entity(
|
self._waze_data.destination = self._get_location_from_entity(
|
||||||
self._destination_entity_id)
|
self._destination_entity_id)
|
||||||
|
|
||||||
self._destination = self._resolve_zone(self._destination)
|
# Get origin from zone name.
|
||||||
self._origin = self._resolve_zone(self._origin)
|
self._waze_data.origin = self._resolve_zone(
|
||||||
|
self._waze_data.origin)
|
||||||
|
|
||||||
if self._destination is not None and self._origin is not None:
|
# Get destination 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:
|
try:
|
||||||
params = WazeRouteCalculator.WazeRouteCalculator(
|
params = self._calc.WazeRouteCalculator(
|
||||||
self._origin, self._destination, self._region,
|
self.origin, self.destination, self.region,
|
||||||
log_lvl=logging.DEBUG)
|
self.vehicle_type, log_lvl=logging.DEBUG)
|
||||||
routes = params.calc_all_routes_info(real_time=self._realtime)
|
routes = params.calc_all_routes_info(real_time=self.realtime)
|
||||||
|
|
||||||
if self._incl_filter is not None:
|
if self.include is not None:
|
||||||
routes = {k: v for k, v in routes.items() if
|
routes = {k: v for k, v in routes.items() if
|
||||||
self._incl_filter.lower() in k.lower()}
|
self.include.lower() in k.lower()}
|
||||||
|
|
||||||
if self._excl_filter is not None:
|
if self.exclude is not None:
|
||||||
routes = {k: v for k, v in routes.items() if
|
routes = {k: v for k, v in routes.items() if
|
||||||
self._excl_filter.lower() not in k.lower()}
|
self.exclude.lower() in k.lower()}
|
||||||
|
|
||||||
route = sorted(routes, key=(lambda key: routes[key][0]))[0]
|
route = sorted(routes, key=(lambda key: routes[key][0]))[0]
|
||||||
duration, distance = routes[route]
|
|
||||||
self._state = {
|
self.duration, distance = routes[route]
|
||||||
'duration': duration,
|
|
||||||
'distance': distance,
|
if self.units == CONF_UNIT_SYSTEM_IMPERIAL:
|
||||||
'route': route,
|
# Convert to miles.
|
||||||
}
|
self.distance = distance / 1.609
|
||||||
except WazeRouteCalculator.WRCError as exp:
|
else:
|
||||||
_LOGGER.error("Error on retrieving data: %s", exp)
|
self.distance = distance
|
||||||
|
|
||||||
|
self.route = route
|
||||||
|
except self._calc.WRCError as exp:
|
||||||
|
_LOGGER.warning("Error on retrieving data: %s", exp)
|
||||||
return
|
return
|
||||||
except KeyError:
|
except KeyError:
|
||||||
_LOGGER.error("Error retrieving data from server")
|
_LOGGER.error("Error retrieving data from server")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user