Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2024.1.0 #3

Merged
merged 17 commits into from
Jan 14, 2024
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ _Integration to integrate with [pyfuelprices][pyfuelprices]._
1. Restart Home Assistant
1. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Fuel Prices"

## Privacy notice

This integration relies entirely on cloud services, alongside this a few libraries are used to geocode provided coordinates into location data for certain providers such as GasBuddy or TankerKoenig.

For reverse geocoding a mix of Nominatim (https://nominatim.org/), these-united-states (https://pypi.org/project/these-united-states/) and reverse-geocode (https://pypi.org/project/reverse-geocode/). This is done to improve performance, for example, looking up provided coordinates with reverse-geocode will allow us to restrict the fuel station search to data providers available in only that country.

Similar to this, this integration will use these-united-states to retrieve the state of given coordinates, and finally Nominatim is used to retrieve the nearest postcode for the TankerKoenig data source.

## Configuration is done in the UI

<!---->
Expand Down
100 changes: 68 additions & 32 deletions custom_components/fuel_prices/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
"""Fuel Prices integration."""

import contextlib
import logging

from datetime import timedelta

from pyfuelprices import FuelPrices
from pyfuelprices.const import PROP_AREA_LAT, PROP_AREA_LONG, PROP_AREA_RADIUS

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.const import (
Platform,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_RADIUS,
CONF_TIMEOUT,
CONF_SCAN_INTERVAL,
)
from homeassistant.core import (
HomeAssistant,
ServiceCall,
Expand All @@ -14,23 +25,47 @@
)
from homeassistant.exceptions import HomeAssistantError

from .const import DOMAIN
from .const import DOMAIN, CONF_AREAS, CONF_SOURCES
from .coordinator import FuelPricesCoordinator

_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.DEVICE_TRACKER]


def _build_configured_areas(hass_areas: dict) -> list[dict]:
module_areas = []
for area in hass_areas:
module_areas.append(
{
PROP_AREA_RADIUS: area[CONF_RADIUS],
PROP_AREA_LAT: area[CONF_LATITUDE],
PROP_AREA_LONG: area[CONF_LONGITUDE],
}
)
return module_areas


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Create ConfigEntry."""
hass.data.setdefault(DOMAIN, {})
_LOGGER.debug("Got request to setup entry.")
sources = entry.options.get(CONF_SOURCES, entry.data.get(CONF_SOURCES, None))
areas = entry.options.get(CONF_AREAS, entry.data.get(CONF_AREAS, None))
timeout = entry.options.get(CONF_TIMEOUT, entry.data.get(CONF_TIMEOUT, 30))
update_interval = entry.options.get(
CONF_SCAN_INTERVAL, entry.data.get(CONF_SCAN_INTERVAL, 1440)
)
default_lat = hass.config.latitude
default_long = hass.config.longitude
try:
fuel_prices: FuelPrices = FuelPrices.create(
enabled_sources=entry.data.get("sources", None)
enabled_sources=sources,
configured_areas=_build_configured_areas(areas),
timeout=timedelta(seconds=timeout),
update_interval=timedelta(minutes=update_interval),
)
await fuel_prices.update()
hass.data[DOMAIN][entry.entry_id] = FuelPricesCoordinator(
with contextlib.suppress(TimeoutError):
await fuel_prices.update()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = FuelPricesCoordinator(
hass, fuel_prices, entry.entry_id
)
except Exception as err:
Expand All @@ -45,44 +80,44 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry):

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

def handle_fuel_lookup(call: ServiceCall) -> ServiceResponse:
async def handle_fuel_lookup(call: ServiceCall) -> ServiceResponse:
"""Handle a fuel lookup call."""
radius = call.data.get("location", {}).get(
"radius", 8046.72
) # this is in meters
radius = radius / 1609
lat = call.data.get("location", {}).get("latitude", 0.0)
long = call.data.get("location", {}).get("longitude", 0.0)
lat = call.data.get("location", {}).get("latitude", default_lat)
long = call.data.get("location", {}).get("longitude", default_long)
fuel_type = call.data.get("type")
return {
"fuels": fuel_prices.find_fuel_from_point((lat, long), radius, fuel_type)
}
try:
return {
"fuels": await fuel_prices.find_fuel_from_point(
(lat, long), radius, fuel_type
)
}
except ValueError as err:
raise HomeAssistantError("Country not available for fuel data.") from err

def handle_fuel_location_lookup(call: ServiceCall) -> ServiceResponse:
async def handle_fuel_location_lookup(call: ServiceCall) -> ServiceResponse:
"""Handle a fuel location lookup call."""
radius = call.data.get("location", {}).get(
"radius", 8046.72
) # this is in meters
radius = radius / 1609
lat = call.data.get("location", {}).get("latitude", 0.0)
long = call.data.get("location", {}).get("longitude", 0.0)
location_ids = fuel_prices.find_fuel_locations_from_point((lat, long), radius)
locations = []
for loc_id in location_ids:
loc = fuel_prices.get_fuel_location(loc_id)
built = {
"name": loc.name,
"last_update": loc.last_updated,
"address": loc.address,
"latitude": loc.lat,
"longitude": loc.long,
"brand": loc.brand,
}
for fuel in loc.available_fuels:
built[fuel.fuel_type] = fuel.cost
locations.append(built)

return {"items": locations, "sources": entry.data.get("sources", [])}
lat = call.data.get("location", {}).get("latitude", default_lat)
long = call.data.get("location", {}).get("longitude", default_long)
try:
locations = await fuel_prices.find_fuel_locations_from_point(
(lat, long), radius
)
except ValueError as err:
raise HomeAssistantError("Country not available for fuel data.") from err
locations_built = []
for loc in locations:
await loc.dynamic_build_fuels()
locations_built.append(loc.__dict__())

return {"items": locations_built, "sources": entry.data.get("sources", [])}

hass.services.async_register(
DOMAIN,
Expand All @@ -105,6 +140,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
_LOGGER.debug("Unloading config entry %s", entry.entry_id)
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
await hass.data[DOMAIN][entry.entry_id].api.client_session.close()
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok
Expand Down
Loading