diff --git a/homeassistant/components/recollect_waste/__init__.py b/homeassistant/components/recollect_waste/__init__.py index 10d39c1fc40..0600d73d8a1 100644 --- a/homeassistant/components/recollect_waste/__init__.py +++ b/homeassistant/components/recollect_waste/__init__.py @@ -14,6 +14,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda from .const import CONF_PLACE_ID, CONF_SERVICE_ID, DATA_COORDINATOR, DOMAIN, LOGGER +DATA_LISTENER = "listener" + DEFAULT_NAME = "recollect_waste" DEFAULT_UPDATE_INTERVAL = timedelta(days=1) @@ -22,7 +24,7 @@ PLATFORMS = ["sensor"] async def async_setup(hass: HomeAssistant, config: dict) -> bool: """Set up the RainMachine component.""" - hass.data[DOMAIN] = {DATA_COORDINATOR: {}} + hass.data[DOMAIN] = {DATA_COORDINATOR: {}, DATA_LISTENER: {}} return True @@ -64,9 +66,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.config_entries.async_forward_entry_setup(entry, component) ) + hass.data[DOMAIN][DATA_LISTENER][entry.entry_id] = entry.add_update_listener( + async_reload_entry + ) + return True +async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Handle an options update.""" + await hass.config_entries.async_reload(entry.entry_id) + + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload an RainMachine config entry.""" unload_ok = all( @@ -79,5 +90,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: ) if unload_ok: hass.data[DOMAIN][DATA_COORDINATOR].pop(entry.entry_id) + cancel_listener = hass.data[DOMAIN][DATA_LISTENER].pop(entry.entry_id) + cancel_listener() return unload_ok diff --git a/homeassistant/components/recollect_waste/config_flow.py b/homeassistant/components/recollect_waste/config_flow.py index 402c143706e..8e208f57cc6 100644 --- a/homeassistant/components/recollect_waste/config_flow.py +++ b/homeassistant/components/recollect_waste/config_flow.py @@ -1,9 +1,13 @@ """Config flow for ReCollect Waste integration.""" +from typing import Optional + from aiorecollect.client import Client from aiorecollect.errors import RecollectError import voluptuous as vol from homeassistant import config_entries +from homeassistant.const import CONF_FRIENDLY_NAME +from homeassistant.core import callback from homeassistant.helpers import aiohttp_client from .const import ( # pylint:disable=unused-import @@ -24,6 +28,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + @staticmethod + @callback + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> config_entries.OptionsFlow: + """Define the config flow to handle options.""" + return RecollectWasteOptionsFlowHandler(config_entry) + async def async_step_import(self, import_config: dict = None) -> dict: """Handle configuration via YAML import.""" return await self.async_step_user(import_config) @@ -62,3 +74,28 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): CONF_SERVICE_ID: user_input[CONF_SERVICE_ID], }, ) + + +class RecollectWasteOptionsFlowHandler(config_entries.OptionsFlow): + """Handle a Recollect Waste options flow.""" + + def __init__(self, entry: config_entries.ConfigEntry): + """Initialize.""" + self._entry = entry + + async def async_step_init(self, user_input: Optional[dict] = None): + """Manage the options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Optional( + CONF_FRIENDLY_NAME, + default=self._entry.options.get(CONF_FRIENDLY_NAME), + ): bool + } + ), + ) diff --git a/homeassistant/components/recollect_waste/sensor.py b/homeassistant/components/recollect_waste/sensor.py index 405d66989bb..d66c2aae0e4 100644 --- a/homeassistant/components/recollect_waste/sensor.py +++ b/homeassistant/components/recollect_waste/sensor.py @@ -1,11 +1,12 @@ """Support for ReCollect Waste sensors.""" -from typing import Callable +from typing import Callable, List +from aiorecollect.client import PickupType import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ATTR_ATTRIBUTION +from homeassistant.const import ATTR_ATTRIBUTION, CONF_FRIENDLY_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers.update_coordinator import ( @@ -35,13 +36,26 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) +@callback +def async_get_pickup_type_names( + entry: ConfigEntry, pickup_types: List[PickupType] +) -> List[str]: + """Return proper pickup type names from their associated objects.""" + return [ + t.friendly_name + if entry.options.get(CONF_FRIENDLY_NAME) and t.friendly_name + else t.name + for t in pickup_types + ] + + async def async_setup_platform( hass: HomeAssistant, config: dict, async_add_entities: Callable, discovery_info: dict = None, ): - """Import Awair configuration from YAML.""" + """Import Recollect Waste configuration from YAML.""" LOGGER.warning( "Loading ReCollect Waste via platform setup is deprecated. " "Please remove it from your configuration." @@ -70,8 +84,7 @@ class ReCollectWasteSensor(CoordinatorEntity): """Initialize the sensor.""" super().__init__(coordinator) self._attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} - self._place_id = entry.data[CONF_PLACE_ID] - self._service_id = entry.data[CONF_SERVICE_ID] + self._entry = entry self._state = None @property @@ -97,7 +110,7 @@ class ReCollectWasteSensor(CoordinatorEntity): @property def unique_id(self) -> str: """Return a unique ID.""" - return f"{self._place_id}{self._service_id}" + return f"{self._entry.data[CONF_PLACE_ID]}{self._entry.data[CONF_SERVICE_ID]}" @callback def _handle_coordinator_update(self) -> None: @@ -120,11 +133,13 @@ class ReCollectWasteSensor(CoordinatorEntity): self._state = pickup_event.date self._attributes.update( { - ATTR_PICKUP_TYPES: [t.name for t in pickup_event.pickup_types], + ATTR_PICKUP_TYPES: async_get_pickup_type_names( + self._entry, pickup_event.pickup_types + ), ATTR_AREA_NAME: pickup_event.area_name, - ATTR_NEXT_PICKUP_TYPES: [ - t.name for t in next_pickup_event.pickup_types - ], + ATTR_NEXT_PICKUP_TYPES: async_get_pickup_type_names( + self._entry, next_pickup_event.pickup_types + ), ATTR_NEXT_PICKUP_DATE: next_date, } ) diff --git a/homeassistant/components/recollect_waste/strings.json b/homeassistant/components/recollect_waste/strings.json index 0cd251c737b..a350b9880fc 100644 --- a/homeassistant/components/recollect_waste/strings.json +++ b/homeassistant/components/recollect_waste/strings.json @@ -14,5 +14,15 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } + }, + "options": { + "step": { + "init": { + "title": "Configure Recollect Waste", + "data": { + "friendly_name": "Use friendly names for pickup types (when possible)" + } + } + } } } diff --git a/homeassistant/components/recollect_waste/translations/en.json b/homeassistant/components/recollect_waste/translations/en.json index 28d73d189b8..e9deabec71b 100644 --- a/homeassistant/components/recollect_waste/translations/en.json +++ b/homeassistant/components/recollect_waste/translations/en.json @@ -14,5 +14,15 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "friendly_name": "Use friendly names for pickup types (when possible)" + }, + "title": "Configure Recollect Waste" + } + } } } \ No newline at end of file diff --git a/tests/components/recollect_waste/test_config_flow.py b/tests/components/recollect_waste/test_config_flow.py index 1f17e1f60d2..ca3fbe8be56 100644 --- a/tests/components/recollect_waste/test_config_flow.py +++ b/tests/components/recollect_waste/test_config_flow.py @@ -8,6 +8,7 @@ from homeassistant.components.recollect_waste import ( DOMAIN, ) from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER +from homeassistant.const import CONF_FRIENDLY_NAME from tests.async_mock import patch from tests.common import MockConfigEntry @@ -45,6 +46,30 @@ async def test_invalid_place_or_service_id(hass): assert result["errors"] == {"base": "invalid_place_or_service_id"} +async def test_options_flow(hass): + """Test config flow options.""" + conf = {CONF_PLACE_ID: "12345", CONF_SERVICE_ID: "12345"} + + config_entry = MockConfigEntry(domain=DOMAIN, unique_id="12345, 12345", data=conf) + config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.recollect_waste.async_setup_entry", return_value=True + ): + await hass.config_entries.async_setup(config_entry.entry_id) + result = await hass.config_entries.options.async_init(config_entry.entry_id) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={CONF_FRIENDLY_NAME: True} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert config_entry.options == {CONF_FRIENDLY_NAME: True} + + async def test_show_form(hass): """Test that the form is served with no input.""" result = await hass.config_entries.flow.async_init(