Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ omit =
homeassistant/components/google/*
homeassistant/components/google_cloud/tts.py
homeassistant/components/google_maps/device_tracker.py
homeassistant/components/google_travel_time/__init__.py
homeassistant/components/google_travel_time/helpers.py
homeassistant/components/google_travel_time/sensor.py
homeassistant/components/gpmdp/media_player.py
homeassistant/components/gpsd/sensor.py
Expand Down
35 changes: 35 additions & 0 deletions homeassistant/components/google_travel_time/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,36 @@
"""The google_travel_time component."""
import asyncio

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

PLATFORMS = ["sensor"]


async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Google Maps Travel Time component."""
return True


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up Google Maps Travel Time from a config entry."""
for component in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, component)
for component in PLATFORMS
]
)
)

return unload_ok
166 changes: 166 additions & 0 deletions homeassistant/components/google_travel_time/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
"""Config flow for Google Maps Travel Time integration."""
import logging

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_API_KEY, CONF_MODE, CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.util import slugify

from .const import (
ALL_LANGUAGES,
ARRIVAL_TIME,
AVOID,
CONF_ARRIVAL_TIME,
CONF_AVOID,
CONF_DEPARTURE_TIME,
CONF_DESTINATION,
CONF_LANGUAGE,
CONF_ORIGIN,
CONF_TIME,
CONF_TIME_TYPE,
CONF_TRAFFIC_MODEL,
CONF_TRANSIT_MODE,
CONF_TRANSIT_ROUTING_PREFERENCE,
CONF_UNITS,
DEFAULT_NAME,
DEPARTURE_TIME,
DOMAIN,
TIME_TYPES,
TRANSIT_PREFS,
TRANSPORT_TYPE,
TRAVEL_MODE,
TRAVEL_MODEL,
UNITS,
)
from .helpers import is_valid_config_entry

_LOGGER = logging.getLogger(__name__)


class GoogleOptionsFlow(config_entries.OptionsFlow):
"""Handle an options flow for Google Travel Time."""

def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
"""Initialize google options flow."""
self.config_entry = config_entry

async def async_step_init(self, user_input=None):
"""Handle the initial step."""
if user_input is not None:
time_type = user_input.pop(CONF_TIME_TYPE)
if time := user_input.pop(CONF_TIME, None):
if time_type == ARRIVAL_TIME:
user_input[CONF_ARRIVAL_TIME] = time
else:
user_input[CONF_DEPARTURE_TIME] = time
return self.async_create_entry(title="", data=user_input)

if CONF_ARRIVAL_TIME in self.config_entry.options:
default_time_type = ARRIVAL_TIME
default_time = self.config_entry.options[CONF_ARRIVAL_TIME]
else:
default_time_type = DEPARTURE_TIME
default_time = self.config_entry.options.get(CONF_ARRIVAL_TIME)

return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Optional(
CONF_MODE, default=self.config_entry.options[CONF_MODE]
): vol.In(TRAVEL_MODE),
vol.Optional(
CONF_LANGUAGE,
default=self.config_entry.options.get(CONF_LANGUAGE),
): vol.In(ALL_LANGUAGES),
vol.Optional(
CONF_AVOID, default=self.config_entry.options.get(CONF_AVOID)
): vol.In(AVOID),
vol.Optional(
CONF_UNITS, default=self.config_entry.options[CONF_UNITS]
): vol.In(UNITS),
vol.Optional(CONF_TIME_TYPE, default=default_time_type): vol.In(
TIME_TYPES
),
vol.Optional(CONF_TIME, default=default_time): cv.string,
vol.Optional(
CONF_TRAFFIC_MODEL,
default=self.config_entry.options.get(CONF_TRAFFIC_MODEL),
): vol.In(TRAVEL_MODEL),
vol.Optional(
CONF_TRANSIT_MODE,
default=self.config_entry.options.get(CONF_TRANSIT_MODE),
): vol.In(TRANSPORT_TYPE),
vol.Optional(
CONF_TRANSIT_ROUTING_PREFERENCE,
default=self.config_entry.options.get(
CONF_TRANSIT_ROUTING_PREFERENCE
),
): vol.In(TRANSIT_PREFS),
}
),
)


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Google Maps Travel Time."""

VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL

@staticmethod
@callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> GoogleOptionsFlow:
"""Get the options flow for this handler."""
return GoogleOptionsFlow(config_entry)

async def async_step_user(self, user_input=None):
"""Handle the initial step."""
errors = {}
if user_input is not None:
if await self.hass.async_add_executor_job(
is_valid_config_entry,
self.hass,
_LOGGER,
user_input[CONF_API_KEY],
user_input[CONF_ORIGIN],
user_input[CONF_DESTINATION],
):
await self.async_set_unique_id(
slugify(
f"{DOMAIN}_{user_input[CONF_ORIGIN]}_{user_input[CONF_DESTINATION]}"
)
)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=user_input.get(
CONF_NAME,
(
f"{DEFAULT_NAME}: {user_input[CONF_ORIGIN]} -> "
f"{user_input[CONF_DESTINATION]}"
),
),
data=user_input,
)

# If we get here, it's because we couldn't connect
errors["base"] = "cannot_connect"

return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_DESTINATION): cv.string,
vol.Required(CONF_ORIGIN): cv.string,
}
),
errors=errors,
)

async_step_import = async_step_user
89 changes: 89 additions & 0 deletions homeassistant/components/google_travel_time/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Constants for Google Travel Time."""
from homeassistant.const import CONF_UNIT_SYSTEM_IMPERIAL, CONF_UNIT_SYSTEM_METRIC

DOMAIN = "google_travel_time"

ATTRIBUTION = "Powered by Google"

CONF_DESTINATION = "destination"
CONF_OPTIONS = "options"
CONF_ORIGIN = "origin"
CONF_TRAVEL_MODE = "travel_mode"
CONF_LANGUAGE = "language"
CONF_AVOID = "avoid"
CONF_UNITS = "units"
CONF_ARRIVAL_TIME = "arrival_time"
CONF_DEPARTURE_TIME = "departure_time"
CONF_TRAFFIC_MODEL = "traffic_model"
CONF_TRANSIT_MODE = "transit_mode"
CONF_TRANSIT_ROUTING_PREFERENCE = "transit_routing_preference"
CONF_TIME_TYPE = "time_type"
CONF_TIME = "time"

ARRIVAL_TIME = "Arrival Time"
DEPARTURE_TIME = "Departure Time"
TIME_TYPES = [ARRIVAL_TIME, DEPARTURE_TIME]

DEFAULT_NAME = "Google Travel Time"

TRACKABLE_DOMAINS = ["device_tracker", "sensor", "zone", "person"]

ALL_LANGUAGES = [
"ar",
"bg",
"bn",
"ca",
"cs",
"da",
"de",
"el",
"en",
"es",
"eu",
"fa",
"fi",
"fr",
"gl",
"gu",
"hi",
"hr",
"hu",
"id",
"it",
"iw",
"ja",
"kn",
"ko",
"lt",
"lv",
"ml",
"mr",
"nl",
"no",
"pl",
"pt",
"pt-BR",
"pt-PT",
"ro",
"ru",
"sk",
"sl",
"sr",
"sv",
"ta",
"te",
"th",
"tl",
"tr",
"uk",
"vi",
"zh-CN",
"zh-TW",
]

AVOID = ["tolls", "highways", "ferries", "indoor"]
TRANSIT_PREFS = ["less_walking", "fewer_transfers"]
TRANSPORT_TYPE = ["bus", "subway", "train", "tram", "rail"]
TRAVEL_MODE = ["driving", "walking", "bicycling", "transit"]
TRAVEL_MODEL = ["best_guess", "pessimistic", "optimistic"]
UNITS = [CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL]
72 changes: 72 additions & 0 deletions homeassistant/components/google_travel_time/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""Helpers for Google Time Travel integration."""
from googlemaps import Client
from googlemaps.distance_matrix import distance_matrix
from googlemaps.exceptions import ApiError

from homeassistant.components.google_travel_time.const import TRACKABLE_DOMAINS
from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE
from homeassistant.helpers import location


def is_valid_config_entry(hass, logger, api_key, origin, destination):
"""Return whether the config entry data is valid."""
origin = resolve_location(hass, logger, origin)
destination = resolve_location(hass, logger, destination)
client = Client(api_key, timeout=10)
try:
distance_matrix(client, origin, destination, mode="driving")
except ApiError:
return False
return True


def resolve_location(hass, logger, loc):
"""Resolve a location."""
if loc.split(".", 1)[0] in TRACKABLE_DOMAINS:
return get_location_from_entity(hass, logger, loc)

return resolve_zone(hass, loc)


def get_location_from_entity(hass, logger, entity_id):
"""Get the location from the entity state or attributes."""
entity = 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
if location.has_location(entity):
return get_location_from_attributes(entity)

# Check if device is in a zone
zone_entity = 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 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


def get_location_from_attributes(entity):
"""Get the lat/long string from an entities attributes."""
attr = entity.attributes
return f"{attr.get(ATTR_LATITUDE)},{attr.get(ATTR_LONGITUDE)}"


def resolve_zone(hass, friendly_name):
"""Resolve a location from a zone's friendly name."""
entities = hass.states.all()
for entity in entities:
if entity.domain == "zone" and entity.name == friendly_name:
return get_location_from_attributes(entity)

return friendly_name
9 changes: 6 additions & 3 deletions homeassistant/components/google_travel_time/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"domain": "google_travel_time",
"name": "Google Maps Travel Time",
"documentation": "https://www.home-assistant.io/integrations/google_travel_time",
"requirements": ["googlemaps==2.5.1"],
"codeowners": []
}
"requirements": [
"googlemaps==2.5.1"
],
"codeowners": [],
"config_flow": true
}
Loading