Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
c8c0749
wip here_weather
eifinger Nov 11, 2019
31e7c2e
first full version of here_weather
eifinger Nov 20, 2019
bdad7e7
execute script.hassfest
eifinger Nov 21, 2019
acd3e67
fix tests
eifinger Nov 21, 2019
e1b26d8
remove redundant update interval constants
eifinger Nov 23, 2019
b639ca8
Migrate to api_key
eifinger Dec 16, 2019
493ac35
script.gen_requirements_all
eifinger Dec 16, 2019
5eff618
fix mock url
eifinger Dec 16, 2019
5e32391
Implement Config Flow
eifinger Jul 12, 2020
75bf364
bump herepy to 3.0.1
eifinger Jul 12, 2020
0d9e999
Increase CodeCov
eifinger Jul 13, 2020
f868a90
Remove weather platform
eifinger Sep 17, 2020
cae2ca3
bump herepy to 3.0.2
eifinger Sep 18, 2020
37b3e6d
update fixture for here_travel_time for 3.0.2
eifinger Sep 18, 2020
18b7dbb
Use more common strings
eifinger Oct 4, 2020
0f460ed
Apply suggestions from code review
eifinger Jan 13, 2021
3c26fc3
fix import patch
eifinger Jan 14, 2021
1f481c8
Apply suggestions
eifinger Jan 20, 2021
a667352
Add weather platform and multiple coordinators
eifinger Feb 14, 2021
e044a4b
Clean up unused entries in config_flow
eifinger Feb 14, 2021
0efd552
Remove mode in tests
eifinger Feb 14, 2021
622a3b6
Test unload correctly
eifinger Feb 14, 2021
a8b9396
Add iot_class to manifest
eifinger Jun 25, 2021
59af2af
Fix mypy
eifinger Jun 25, 2021
131f995
Fix tests
eifinger Jun 26, 2021
de80121
Apply suggestions from code review
eifinger Jul 3, 2021
5b9f991
style: rename config_entry to entry
eifinger Jul 3, 2021
b617a61
remove None check in get_attribute_from_here_data
eifinger Jul 22, 2021
1d828e7
simplify except KeyError
eifinger Jul 22, 2021
8ed8ce3
Return correct types for weather attributes
eifinger Jul 22, 2021
b7a2ed7
Only call async_add_entities once
eifinger Jul 22, 2021
6703563
Rename config_entry to entry
eifinger Jul 22, 2021
8fadeb3
Use .items() to iterate
eifinger Jul 22, 2021
8c3d47d
Use generator expression
eifinger Jul 22, 2021
5e0c878
remove title from strings.json
eifinger Jul 22, 2021
bf7f9ad
remove fire start event
eifinger Jul 22, 2021
b8b8e74
Move test_unload_entry
eifinger Jul 22, 2021
ff12f84
Inherit from SensorEntity
eifinger Jul 22, 2021
34104ed
Use known api keys from entries
eifinger Jul 22, 2021
1315639
Set unique id
eifinger Jul 22, 2021
8a0dd23
remove CONNECTION_CLASS
eifinger Jul 22, 2021
76f1ce5
remove localTime
eifinger Jul 22, 2021
4094957
add device_class
eifinger Jul 22, 2021
388ee89
Remove options
eifinger Jul 22, 2021
a1ae5ba
Fix unique_id and state
eifinger Jul 22, 2021
c95d5e4
herepy 3.5.3 for here_travel_time
eifinger Jul 22, 2021
9a9f9fe
Add test for observation
eifinger Jul 22, 2021
ac010a6
Simplify try block
eifinger Jul 23, 2021
e1b7683
Use PERCENTAGE
eifinger Jul 23, 2021
32ed66d
Use relative import
eifinger Jul 23, 2021
c4a62e0
Use custom UpdateCoordinator
eifinger Jul 23, 2021
c8e5b2b
Fix known_api_keys for configflow
eifinger Jul 23, 2021
b89c110
Remove unused constants
eifinger Jul 23, 2021
8f53aa9
fix typo
eifinger Jul 23, 2021
6fe8749
Remove known_api_key feature
eifinger Jul 23, 2021
1720b37
Remove update_rate throttling
eifinger Jul 23, 2021
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
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ homeassistant/components/hassio/* @home-assistant/supervisor
homeassistant/components/heatmiser/* @andylockran
homeassistant/components/heos/* @andrewsayre
homeassistant/components/here_travel_time/* @eifinger
homeassistant/components/here_weather/* @eifinger
homeassistant/components/hikvision/* @mezz64
homeassistant/components/hikvisioncam/* @fbradyirl
homeassistant/components/hisense_aehw4a1/* @bannhead
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/here_travel_time/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "here_travel_time",
"name": "HERE Travel Time",
"documentation": "https://www.home-assistant.io/integrations/here_travel_time",
"requirements": ["herepy==2.0.0"],
"requirements": ["herepy==3.5.3"],
"codeowners": ["@eifinger"],
"iot_class": "cloud_polling"
}
107 changes: 107 additions & 0 deletions homeassistant/components/here_weather/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""The here_weather component."""
from __future__ import annotations

from datetime import timedelta
import logging

import async_timeout
import herepy

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_API_KEY,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_UNIT_SYSTEM_METRIC,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import CONF_MODES, DEFAULT_SCAN_INTERVAL, DOMAIN

_LOGGER = logging.getLogger(__name__)

PLATFORMS = ["sensor", "weather"]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up here_weather from a config entry."""
here_weather_coordinators = {}
for mode in CONF_MODES:
coordinator = HEREWeatherDataUpdateCoordinator(hass, entry, mode)
await coordinator.async_config_entry_first_refresh()
here_weather_coordinators[mode] = coordinator
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = here_weather_coordinators

hass.config_entries.async_setup_platforms(entry, PLATFORMS)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok


class HEREWeatherDataUpdateCoordinator(DataUpdateCoordinator):
"""Get the latest data from HERE."""

def __init__(self, hass: HomeAssistant, entry: ConfigEntry, mode: str) -> None:
"""Initialize the data object."""
self.here_client = herepy.DestinationWeatherApi(entry.data[CONF_API_KEY])
self.latitude = entry.data[CONF_LATITUDE]
self.longitude = entry.data[CONF_LONGITUDE]
self.weather_product_type = herepy.WeatherProductType[mode]

super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
)

async def _async_update_data(self) -> list:
"""Perform data update."""
try:
async with async_timeout.timeout(10):
data = await self.hass.async_add_executor_job(self._get_data)
return data
except herepy.InvalidRequestError as error:
raise UpdateFailed(
f"Unable to fetch data from HERE: {error.message}"
) from error

def _get_data(self):
"""Get the latest data from HERE."""
is_metric = self.hass.config.units.name == CONF_UNIT_SYSTEM_METRIC
data = self.here_client.weather_for_coordinates(
self.latitude,
self.longitude,
self.weather_product_type,
metric=is_metric,
)
return extract_data_from_payload_for_product_type(
data, self.weather_product_type
)


def extract_data_from_payload_for_product_type(
data: herepy.DestinationWeatherResponse, product_type: herepy.WeatherProductType
) -> list:
"""Extract the actual data from the HERE payload."""
if product_type == herepy.WeatherProductType.forecast_astronomy:
return data.astronomy["astronomy"]
if product_type == herepy.WeatherProductType.observation:
return data.observations["location"][0]["observation"]
if product_type == herepy.WeatherProductType.forecast_7days:
return data.forecasts["forecastLocation"]["forecast"]
if product_type == herepy.WeatherProductType.forecast_7days_simple:
return data.dailyForecasts["forecastLocation"]["forecast"]
if product_type == herepy.WeatherProductType.forecast_hourly:
return data.hourlyForecasts["forecastLocation"]["forecast"]
_LOGGER.debug("Payload malformed: %s", data)
raise UpdateFailed("Payload malformed")
82 changes: 82 additions & 0 deletions homeassistant/components/here_weather/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Config flow for here_weather integration."""
from __future__ import annotations

import herepy
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv

from .const import DEFAULT_MODE, DOMAIN


async def async_validate_user_input(hass: HomeAssistant, user_input: dict) -> None:
"""Validate the user_input containing coordinates."""
here_client = herepy.DestinationWeatherApi(user_input[CONF_API_KEY])
await hass.async_add_executor_job(
here_client.weather_for_coordinates,
user_input[CONF_LATITUDE],
user_input[CONF_LONGITUDE],
herepy.WeatherProductType[DEFAULT_MODE],
)


class HereWeatherConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for here_weather."""

VERSION = 1

async def async_step_user(self, user_input=None):
"""Handle the initial step."""
errors = {}
if user_input is not None:
await self.async_set_unique_id(_unique_id(user_input))
self._abort_if_unique_id_configured()
try:
await async_validate_user_input(self.hass, user_input)
except herepy.InvalidRequestError:
errors["base"] = "invalid_request"
except herepy.UnauthorizedError:
errors["base"] = "unauthorized"
else:
return self.async_create_entry(
title=user_input[CONF_NAME], data=user_input
)
return self.async_show_form(
step_id="user",
data_schema=self._get_schema(user_input),
errors=errors,
)

def _get_schema(self, user_input: dict | None) -> vol.Schema:
if user_input is not None:
return vol.Schema(
{
vol.Required(CONF_API_KEY, default=user_input[CONF_API_KEY]): str,
vol.Required(CONF_NAME, default=user_input[CONF_NAME]): str,
vol.Required(
CONF_LATITUDE, default=user_input[CONF_LATITUDE]
): cv.latitude,
vol.Required(
CONF_LONGITUDE, default=user_input[CONF_LONGITUDE]
): cv.longitude,
}
)
return vol.Schema(
{
vol.Required(CONF_API_KEY): str,
vol.Required(CONF_NAME, default=DOMAIN): str,
vol.Required(
CONF_LATITUDE, default=self.hass.config.latitude
): cv.latitude,
vol.Required(
CONF_LONGITUDE, default=self.hass.config.longitude
): cv.longitude,
}
)


def _unique_id(user_input: dict) -> str:
return f"{user_input[CONF_LATITUDE]}_{user_input[CONF_LONGITUDE]}"
Loading