From b53ed84553aae7d6ec5062bcc34171864f744587 Mon Sep 17 00:00:00 2001 From: Jordan H Date: Sun, 14 Jan 2024 12:46:33 +0000 Subject: [PATCH] 2024.1.0 (#3) --- README.md | 8 + custom_components/fuel_prices/__init__.py | 100 +++++-- custom_components/fuel_prices/config_flow.py | 273 +++++++++++++++++- custom_components/fuel_prices/coordinator.py | 6 +- .../fuel_prices/device_tracker.py | 25 +- custom_components/fuel_prices/entity.py | 7 +- custom_components/fuel_prices/manifest.json | 37 +-- custom_components/fuel_prices/strings.json | 70 ++++- .../fuel_prices/translations/en.json | 70 ++++- requirements.txt | 2 +- 10 files changed, 515 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index ff0d219..660e2e9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/custom_components/fuel_prices/__init__.py b/custom_components/fuel_prices/__init__.py index 72df5b7..553e84f 100644 --- a/custom_components/fuel_prices/__init__.py +++ b/custom_components/fuel_prices/__init__.py @@ -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, @@ -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: @@ -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, @@ -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 diff --git a/custom_components/fuel_prices/config_flow.py b/custom_components/fuel_prices/config_flow.py index 7eb940e..e3f0ff2 100644 --- a/custom_components/fuel_prices/config_flow.py +++ b/custom_components/fuel_prices/config_flow.py @@ -1,9 +1,10 @@ -"""Config flow for MSFT Family Safety.""" +"""Config flow for Fuel Prices.""" import logging from typing import Any +from homeassistant.config_entries import ConfigEntry, OptionsFlow -from pyfuelprices import SOURCE_MAP +from pyfuelprices.sources.mapping import SOURCE_MAP, COUNTRY_MAP import voluptuous as vol from homeassistant import config_entries @@ -11,7 +12,15 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import selector from homeassistant.helpers import config_validation as cv -from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_RADIUS, CONF_NAME +from homeassistant.core import callback +from homeassistant.const import ( + CONF_LATITUDE, + CONF_LONGITUDE, + CONF_RADIUS, + CONF_NAME, + CONF_TIMEOUT, + CONF_SCAN_INTERVAL, +) from .const import DOMAIN, NAME, CONF_AREAS, CONF_SOURCES @@ -47,6 +56,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): configured_sources = [] configuring_area = {} configuring_index = -1 + timeout = None + interval = None @property def configured_area_names(self) -> list[str]: @@ -64,10 +75,25 @@ async def async_step_user( # users should use the options flow to adjust areas and sources. await self.async_set_unique_id(NAME) self._abort_if_unique_id_configured() + self.configured_areas = [] + self.configured_sources = [] + self.configuring_area = {} + self.configuring_index = -1 + self.timeout = 10 + self.interval = 1440 + # add the home location as a default (this can optionally be removed). + self.configured_areas.append( + { + CONF_NAME: self.hass.config.location_name, + CONF_LATITUDE: self.hass.config.latitude, + CONF_LONGITUDE: self.hass.config.longitude, + CONF_RADIUS: 10.0, + } + ) return await self.async_step_main_menu() async def async_step_main_menu(self, _: None = None): - """Main menu.""" + """Display configuration menu.""" return self.async_show_menu( step_id="main_menu", menu_options={ @@ -78,7 +104,238 @@ async def async_step_main_menu(self, _: None = None): ) async def async_step_sources(self, user_input: dict[str, Any] | None = None): - """Sources configuration step.""" + """Set data source config.""" + if user_input is not None: + self.configured_sources = user_input[CONF_SOURCES] + return await self.async_step_main_menu(None) + return self.async_show_form( + step_id="sources", + data_schema=vol.Schema( + { + vol.Optional( + CONF_SOURCES, default=self.configured_sources + ): selector.SelectSelector( + selector.SelectSelectorConfig( + mode=selector.SelectSelectorMode.DROPDOWN, + options=list(SOURCE_MAP), + multiple=True, + ) + ), + vol.Optional( + CONF_TIMEOUT, + default=self.timeout, + ): selector.NumberSelector( + selector.NumberSelectorConfig( + mode=selector.NumberSelectorMode.BOX, + min=5, + max=60, + unit_of_measurement="s", + ) + ), + vol.Optional( + CONF_SCAN_INTERVAL, + default=self.interval, + ): selector.NumberSelector( + selector.NumberSelectorConfig( + mode=selector.NumberSelectorMode.BOX, + min=120, + max=1440, + unit_of_measurement="m", + ) + ), + } + ), + ) + + async def async_step_area_menu(self, _: None = None) -> FlowResult: + """Show the area menu.""" + return self.async_show_menu( + step_id="area_menu", + menu_options={ + "area_create": "Define a new area", + "area_update_select": "Update an area", + "area_delete": "Delete an area", + "main_menu": "Return to main menu", + }, + ) + + async def async_step_area_create(self, user_input: dict[str, Any] | None = None): + """Handle an area configuration.""" + errors: dict[str, str] = {} + if user_input is not None: + self.configured_areas.append( + { + CONF_NAME: user_input[CONF_NAME], + CONF_LATITUDE: user_input[CONF_LATITUDE], + CONF_LONGITUDE: user_input[CONF_LONGITUDE], + CONF_RADIUS: user_input[CONF_RADIUS], + } + ) + return await self.async_step_area_menu() + return self.async_show_form( + step_id="area_create", data_schema=AREA_SCHEMA, errors=errors + ) + + async def async_step_area_update_select( + self, user_input: dict[str, Any] | None = None + ): + """Show a menu to allow the user to select what option to update.""" + if user_input is not None: + for i, data in enumerate(self.configured_areas): + if self.configured_areas[i]["name"] == user_input[CONF_NAME]: + self.configuring_area = data + self.configuring_index = i + break + return await self.async_step_area_update() + if len(self.configured_areas) > 0: + return self.async_show_form( + step_id="area_update_select", + data_schema=vol.Schema( + { + vol.Required(CONF_NAME): selector.SelectSelector( + selector.SelectSelectorConfig( + mode=selector.SelectSelectorMode.LIST, + options=self.configured_area_names, + ) + ) + } + ), + ) + return await self.async_step_area_menu() + + async def async_step_area_update(self, user_input: dict[str, Any] | None = None): + """Handle an area update.""" + errors: dict[str, str] = {} + if user_input is not None: + self.configured_areas.pop(self.configuring_index) + self.configured_areas.append( + { + CONF_NAME: user_input[CONF_NAME], + CONF_LATITUDE: user_input[CONF_LATITUDE], + CONF_LONGITUDE: user_input[CONF_LONGITUDE], + CONF_RADIUS: user_input[CONF_RADIUS], + } + ) + return await self.async_step_area_menu() + return self.async_show_form( + step_id="area_update", + data_schema=vol.Schema( + { + vol.Required( + CONF_NAME, default=self.configuring_area[CONF_NAME] + ): selector.TextSelector(), + vol.Required( + CONF_RADIUS, default=self.configuring_area[CONF_RADIUS] + ): selector.NumberSelector( + selector.NumberSelectorConfig( + mode=selector.NumberSelectorMode.BOX, + unit_of_measurement="miles", + min=1, + max=50, + step=0.1, + ) + ), + vol.Inclusive( + CONF_LATITUDE, + "coordinates", + "Latitude and longitude must exist together", + default=self.configuring_area[CONF_LATITUDE], + ): cv.latitude, + vol.Inclusive( + CONF_LONGITUDE, + "coordinates", + "Latitude and longitude must exist together", + default=self.configuring_area[CONF_LONGITUDE], + ): cv.longitude, + } + ), + errors=errors, + ) + + async def async_step_area_delete(self, user_input: dict[str, Any] | None = None): + """Delete a configured area.""" + if user_input is not None: + for i, data in enumerate(self.configured_areas): + if data["name"] == user_input[CONF_NAME]: + self.configured_areas.pop(i) + break + return await self.async_step_area_menu() + if len(self.configured_areas) > 0: + return self.async_show_form( + step_id="area_delete", + data_schema=vol.Schema( + { + vol.Required(CONF_NAME): selector.SelectSelector( + selector.SelectSelectorConfig( + mode=selector.SelectSelectorMode.LIST, + options=self.configured_area_names, + ) + ) + } + ), + ) + return await self.async_step_area_menu() + + async def async_step_finished(self, user_input: dict[str, Any] | None = None): + """Save configuration.""" + errors: dict[str, str] = {} + if user_input is not None: + if len(self.configured_sources) > 0: + user_input[CONF_SOURCES] = self.configured_sources + elif self.hass.config.country is not None: + user_input[CONF_SOURCES] = COUNTRY_MAP.get(self.hass.config.country) + else: + user_input[CONF_SOURCES] = list(SOURCE_MAP) + user_input[CONF_AREAS] = self.configured_areas + return self.async_create_entry(title=NAME, data=user_input) + return self.async_show_form(step_id="finished", errors=errors, last_step=True) + + @staticmethod + @callback + def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: + """Return option flow.""" + return FuelPricesOptionsFlow(config_entry) + + +class FuelPricesOptionsFlow(config_entries.OptionsFlow): + """OptionsFlow for fuel_prices module.""" + + configured_areas: list[dict] = [] + configured_sources = [] + configuring_area = {} + configuring_index = -1 + + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + """Initialize options flow.""" + self.config_entry = config_entry + self.configured_areas = self.config_entry.data.get(CONF_AREAS, []) + self.configured_sources = self.config_entry.data.get(CONF_SOURCES, []) + + @property + def configured_area_names(self) -> list[str]: + """Return a list of area names.""" + items = [] + for area in self.configured_areas: + items.append(area["name"]) + return items + + async def async_step_init(self, _: None = None): + """User init option flow.""" + return await self.async_step_main_menu() + + async def async_step_main_menu(self, _: None = None): + """Display configuration menu.""" + return self.async_show_menu( + step_id="main_menu", + menu_options={ + "area_menu": "Configure areas to create devices/sensors", + "sources": "Configure data collector sources", + "finished": "Complete re-configuration", + }, + ) + + async def async_step_sources(self, user_input: dict[str, Any] | None = None): + """Set data source config.""" if user_input is not None: self.configured_sources = user_input[CONF_SOURCES] return await self.async_step_main_menu(None) @@ -91,7 +348,7 @@ async def async_step_sources(self, user_input: dict[str, Any] | None = None): ): selector.SelectSelector( selector.SelectSelectorConfig( mode=selector.SelectSelectorMode.DROPDOWN, - options=[k for k in SOURCE_MAP], + options=list(SOURCE_MAP), multiple=True, ) ) @@ -229,13 +486,13 @@ async def async_step_area_delete(self, user_input: dict[str, Any] | None = None) return await self.async_step_area_menu() async def async_step_finished(self, user_input: dict[str, Any] | None = None): - """Final confirmation step.""" + """Save configuration.""" errors: dict[str, str] = {} if user_input is not None: user_input[CONF_SOURCES] = ( self.configured_sources if len(self.configured_sources) > 0 - else [k for k in SOURCE_MAP] + else list(SOURCE_MAP) ) user_input[CONF_AREAS] = self.configured_areas return self.async_create_entry(title=NAME, data=user_input) diff --git a/custom_components/fuel_prices/coordinator.py b/custom_components/fuel_prices/coordinator.py index 51d10d9..3cbdc76 100644 --- a/custom_components/fuel_prices/coordinator.py +++ b/custom_components/fuel_prices/coordinator.py @@ -21,7 +21,7 @@ def __init__(self, hass: HomeAssistant, api: FuelPrices, name: str) -> None: hass=hass, logger=_LOGGER, name=name, - update_interval=timedelta(seconds=7200), + update_interval=timedelta(minutes=30), ) self.api: FuelPrices = api @@ -30,5 +30,9 @@ async def _async_update_data(self): try: async with async_timeout.timeout(240): return await self.api.update() + except TimeoutError as err: + _LOGGER.error("Timeout updating fuel price data: %s", err) + except TypeError as err: + _LOGGER.error("Error updating fuel price data: %s", err) except Exception as err: raise UpdateFailed(f"Error communicating with API {err}") from err diff --git a/custom_components/fuel_prices/device_tracker.py b/custom_components/fuel_prices/device_tracker.py index 01b63d5..abe7bfb 100644 --- a/custom_components/fuel_prices/device_tracker.py +++ b/custom_components/fuel_prices/device_tracker.py @@ -14,6 +14,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType +from pyfuelprices.const import PROP_FUEL_LOCATION_SOURCE from .const import CONF_AREAS, DOMAIN from .entity import FeulStationEntity from .coordinator import FuelPricesCoordinator @@ -24,22 +25,27 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback ) -> None: - """Setup fuel prices device tracker component.""" + """Integration platform creation.""" cooridinator: FuelPricesCoordinator = hass.data[DOMAIN][entry.entry_id] areas = entry.data[CONF_AREAS] entities = [] + found_entities = [] for area in areas: _LOGGER.debug("Registering entities for area %s", area[CONF_NAME]) - for station_id in cooridinator.api.find_fuel_locations_from_point( - point=(area[CONF_LATITUDE], area[CONF_LONGITUDE]), radius=area[CONF_RADIUS] + for station in await cooridinator.api.find_fuel_locations_from_point( + coordinates=(area[CONF_LATITUDE], area[CONF_LONGITUDE]), + radius=area[CONF_RADIUS], ): - entities.append( - FeulStationTracker( - coordinator=cooridinator, - fuel_station_id=station_id, - entity_id="devicetracker", + if station.id not in found_entities: + entities.append( + FeulStationTracker( + coordinator=cooridinator, + fuel_station_id=station.id, + entity_id="devicetracker", + source=station.props[PROP_FUEL_LOCATION_SOURCE], + ) ) - ) + found_entities.append(station.id) async_add_entities(entities, True) @@ -100,6 +106,7 @@ def state_attributes(self) -> dict[str, StateType]: attr: dict[str, StateType] = { ATTR_SOURCE_TYPE: self.source_type, **self._get_fuels, + **self._fuel_station.__dict__(), } if self.latitude is not None and self.longitude is not None: attr[ATTR_LATITUDE] = self.latitude diff --git a/custom_components/fuel_prices/entity.py b/custom_components/fuel_prices/entity.py index 18604ad..df3fdcc 100644 --- a/custom_components/fuel_prices/entity.py +++ b/custom_components/fuel_prices/entity.py @@ -10,18 +10,21 @@ class FeulStationEntity(CoordinatorEntity): """Represents a fuel station.""" def __init__( - self, coordinator: FuelPricesCoordinator, fuel_station_id, entity_id + self, coordinator: FuelPricesCoordinator, fuel_station_id, entity_id, source ) -> None: """Initialize.""" super().__init__(coordinator) self.coordinator: FuelPricesCoordinator = coordinator self._fuel_station_id = fuel_station_id self._entity_id = entity_id + self._fuel_station_source = str(source).lower() @property def _fuel_station(self): """Return the fuel station.""" - return self.coordinator.api.get_fuel_location(self._fuel_station_id) + return self.coordinator.api.configured_sources[ + self._fuel_station_source + ].location_cache[self._fuel_station_id] @property def unique_id(self) -> str | None: diff --git a/custom_components/fuel_prices/manifest.json b/custom_components/fuel_prices/manifest.json index ccd36b1..044de5b 100644 --- a/custom_components/fuel_prices/manifest.json +++ b/custom_components/fuel_prices/manifest.json @@ -1,19 +1,20 @@ { - "domain": "fuel_prices", - "name": "Fuel Prices", - "codeowners": [ - "@pantherale0" - ], - "config_flow": true, - "documentation": "https://github.com/pantherale0/ha-fuelprices", - "integration_type": "service", - "iot_class": "cloud_polling", - "issue_tracker": "https://github.com/pantherale0/ha-fuelprices/issues", - "requirements": [ - "pyfuelprices==1.1.1", - "geopy" - ], - "ssdp": [], - "version": "0.0.0", - "zeroconf": [] - } \ No newline at end of file + "domain": "fuel_prices", + "name": "Fuel Prices", + "codeowners": [ + "@pantherale0" + ], + "config_flow": true, + "documentation": "https://github.com/pantherale0/ha-fuelprices", + "integration_type": "service", + "iot_class": "cloud_polling", + "issue_tracker": "https://github.com/pantherale0/ha-fuelprices/issues", + "requirements": [ + "reverse-geocode==1.4.1", + "these-united-states==1.1.0.21", + "pyfuelprices==2.1.11" + ], + "ssdp": [], + "version": "0.0.0", + "zeroconf": [] +} \ No newline at end of file diff --git a/custom_components/fuel_prices/strings.json b/custom_components/fuel_prices/strings.json index eb79f12..ae7a634 100644 --- a/custom_components/fuel_prices/strings.json +++ b/custom_components/fuel_prices/strings.json @@ -8,13 +8,15 @@ }, "sources": { "title": "Configure data collection sources", - "description": "Using this menu you can change what providers the integration will collect data from.", + "description": "Using this menu you can change what providers the integration will collect data from. By default it will use all data sources available for your current country as configured in Home Assistant.", "data": { - "sources": "Data source(s)" + "sources": "Data source(s)", + "timeout": "Data source timeout", + "scan_interval": "Data source update interval" } }, "area_menu": { - "title": "Configure areas to register devices and sensors", + "title": "Configure areas to register devices and sensors, by default your home location has already been added automatically with a radius of 20 miles, this can be removed or changed if needed.", "menu_options": { "area_create": "Create an area", "area_update_select": "Update an area", @@ -54,11 +56,11 @@ "name": "Area name" } } + }, + "abort": { + "already_configured": "For performance considerations and memory efficiency, only one instance of this integration is allowed." } }, - "error": { - "already_configured": "Only one instance of this integration is allowed." - }, "services": { "find_fuels": { "name": "Find fuel prices from location", @@ -84,5 +86,61 @@ } } } + }, + "options": { + "step": { + "finished": { + "title": "Fuel Prices", + "description": "Click submit to finish setup" + }, + "sources": { + "title": "Configure data collection sources", + "description": "Using this menu you can change what providers the integration will collect data from.", + "data": { + "sources": "Data source(s)" + } + }, + "area_menu": { + "title": "Configure areas to register devices and sensors", + "menu_options": { + "area_create": "Create an area", + "area_update_select": "Update an area", + "area_delete": "Delete an area", + "main_menu": "Return to main menu" + } + }, + "area_create": { + "title": "Create an area", + "description": "Using this menu you can create areas to register devices and sensors. This integration will create a device for each fuel station discovered, under this a sensor will be created for each fuel type.", + "data": { + "name": "Area name (must be unique)", + "radius": "Maximum search radius", + "latitude": "Latitude for the center of the search location", + "longitude": "Longitude for the center of the search location" + } + }, + "area_update_select": { + "title": "Select area to update", + "data": { + "name": "Area name" + } + }, + "area_update": { + "title": "Create an area", + "description": "Using this menu you can create areas to register devices and sensors. This integration will create a device for each fuel station discovered, under this a sensor will be created for each fuel type.", + "data": { + "name": "Area name (must be unique)", + "radius": "Maximum search radius", + "latitude": "Latitude for the center of the search location", + "longitude": "Longitude for the center of the search location" + } + }, + "area_delete": { + "title": "Select area to delete", + "data": { + "name": "Area name" + } + } + } } } \ No newline at end of file diff --git a/custom_components/fuel_prices/translations/en.json b/custom_components/fuel_prices/translations/en.json index eb79f12..ae7a634 100644 --- a/custom_components/fuel_prices/translations/en.json +++ b/custom_components/fuel_prices/translations/en.json @@ -8,13 +8,15 @@ }, "sources": { "title": "Configure data collection sources", - "description": "Using this menu you can change what providers the integration will collect data from.", + "description": "Using this menu you can change what providers the integration will collect data from. By default it will use all data sources available for your current country as configured in Home Assistant.", "data": { - "sources": "Data source(s)" + "sources": "Data source(s)", + "timeout": "Data source timeout", + "scan_interval": "Data source update interval" } }, "area_menu": { - "title": "Configure areas to register devices and sensors", + "title": "Configure areas to register devices and sensors, by default your home location has already been added automatically with a radius of 20 miles, this can be removed or changed if needed.", "menu_options": { "area_create": "Create an area", "area_update_select": "Update an area", @@ -54,11 +56,11 @@ "name": "Area name" } } + }, + "abort": { + "already_configured": "For performance considerations and memory efficiency, only one instance of this integration is allowed." } }, - "error": { - "already_configured": "Only one instance of this integration is allowed." - }, "services": { "find_fuels": { "name": "Find fuel prices from location", @@ -84,5 +86,61 @@ } } } + }, + "options": { + "step": { + "finished": { + "title": "Fuel Prices", + "description": "Click submit to finish setup" + }, + "sources": { + "title": "Configure data collection sources", + "description": "Using this menu you can change what providers the integration will collect data from.", + "data": { + "sources": "Data source(s)" + } + }, + "area_menu": { + "title": "Configure areas to register devices and sensors", + "menu_options": { + "area_create": "Create an area", + "area_update_select": "Update an area", + "area_delete": "Delete an area", + "main_menu": "Return to main menu" + } + }, + "area_create": { + "title": "Create an area", + "description": "Using this menu you can create areas to register devices and sensors. This integration will create a device for each fuel station discovered, under this a sensor will be created for each fuel type.", + "data": { + "name": "Area name (must be unique)", + "radius": "Maximum search radius", + "latitude": "Latitude for the center of the search location", + "longitude": "Longitude for the center of the search location" + } + }, + "area_update_select": { + "title": "Select area to update", + "data": { + "name": "Area name" + } + }, + "area_update": { + "title": "Create an area", + "description": "Using this menu you can create areas to register devices and sensors. This integration will create a device for each fuel station discovered, under this a sensor will be created for each fuel type.", + "data": { + "name": "Area name (must be unique)", + "radius": "Maximum search radius", + "latitude": "Latitude for the center of the search location", + "longitude": "Longitude for the center of the search location" + } + }, + "area_delete": { + "title": "Select area to delete", + "data": { + "name": "Area name" + } + } + } } } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 2fef231..429674c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,4 @@ colorlog==6.7.0 homeassistant==2023.8.0 pip>=21.0,<23.2 ruff==0.0.292 -pyfuelprices==1.1.1 \ No newline at end of file +pyfuelprices==2.1.10 \ No newline at end of file