diff --git a/.coveragerc b/.coveragerc index c891a73e290..a34e137f61c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1140,6 +1140,7 @@ omit = homeassistant/components/tuya/switch.py homeassistant/components/tuya/util.py homeassistant/components/tuya/vacuum.py + homeassistant/components/twentemilieu/__init__.py homeassistant/components/twentemilieu/const.py homeassistant/components/twentemilieu/sensor.py homeassistant/components/twilio_call/notify.py diff --git a/.strict-typing b/.strict-typing index 01295668a64..daed6f3434a 100644 --- a/.strict-typing +++ b/.strict-typing @@ -133,6 +133,7 @@ homeassistant.components.tplink.* homeassistant.components.tractive.* homeassistant.components.tradfri.* homeassistant.components.tts.* +homeassistant.components.twentemilieu.* homeassistant.components.upcloud.* homeassistant.components.uptime.* homeassistant.components.uptimerobot.* diff --git a/homeassistant/components/twentemilieu/__init__.py b/homeassistant/components/twentemilieu/__init__.py index 81e0a040333..7b4e4084364 100644 --- a/homeassistant/components/twentemilieu/__init__.py +++ b/homeassistant/components/twentemilieu/__init__.py @@ -1,10 +1,9 @@ """Support for Twente Milieu.""" from __future__ import annotations -import asyncio -from datetime import timedelta +from datetime import date, timedelta -from twentemilieu import TwenteMilieu +from twentemilieu import TwenteMilieu, WasteType import voluptuous as vol from homeassistant.config_entries import ConfigEntry @@ -12,17 +11,9 @@ from homeassistant.const import CONF_ID from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.dispatcher import async_dispatcher_send -from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from .const import ( - CONF_HOUSE_LETTER, - CONF_HOUSE_NUMBER, - CONF_POST_CODE, - DATA_UPDATE, - DOMAIN, -) +from .const import CONF_HOUSE_LETTER, CONF_HOUSE_NUMBER, CONF_POST_CODE, DOMAIN, LOGGER SCAN_INTERVAL = timedelta(seconds=3600) @@ -32,35 +23,6 @@ SERVICE_SCHEMA = vol.Schema({vol.Optional(CONF_ID): cv.string}) PLATFORMS = ["sensor"] -async def _update_twentemilieu(hass: HomeAssistant, unique_id: str | None) -> None: - """Update Twente Milieu.""" - if unique_id is not None: - twentemilieu = hass.data[DOMAIN].get(unique_id) - if twentemilieu is not None: - await twentemilieu.update() - async_dispatcher_send(hass, DATA_UPDATE, unique_id) - else: - await asyncio.wait( - [twentemilieu.update() for twentemilieu in hass.data[DOMAIN].values()] - ) - - for uid in hass.data[DOMAIN]: - async_dispatcher_send(hass, DATA_UPDATE, uid) - - -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the Twente Milieu components.""" - - async def update(call) -> None: - """Service call to manually update the data.""" - unique_id = call.data.get(CONF_ID) - await _update_twentemilieu(hass, unique_id) - - hass.services.async_register(DOMAIN, SERVICE_UPDATE, update, schema=SERVICE_SCHEMA) - - return True - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Twente Milieu from a config entry.""" session = async_get_clientsession(hass) @@ -71,24 +33,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: session=session, ) - unique_id = entry.data[CONF_ID] - hass.data.setdefault(DOMAIN, {})[unique_id] = twentemilieu + coordinator: DataUpdateCoordinator[ + dict[WasteType, date | None] + ] = DataUpdateCoordinator( + hass, + LOGGER, + name=DOMAIN, + update_interval=SCAN_INTERVAL, + update_method=twentemilieu.update, + ) + await coordinator.async_config_entry_first_refresh() + # For backwards compat, set unique ID + if entry.unique_id is None: + hass.config_entries.async_update_entry(entry, unique_id=entry.data[CONF_ID]) + + hass.data.setdefault(DOMAIN, {})[entry.data[CONF_ID]] = coordinator hass.config_entries.async_setup_platforms(entry, PLATFORMS) - async def _interval_update(now=None) -> None: - """Update Twente Milieu data.""" - await _update_twentemilieu(hass, unique_id) - - async_track_time_interval(hass, _interval_update, SCAN_INTERVAL) - return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload Twente Milieu config entry.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - del hass.data[DOMAIN][entry.data[CONF_ID]] - + if unload_ok: + del hass.data[DOMAIN][entry.entry_id] return unload_ok diff --git a/homeassistant/components/twentemilieu/config_flow.py b/homeassistant/components/twentemilieu/config_flow.py index 6a70fbbdcbe..7a00222fe9b 100644 --- a/homeassistant/components/twentemilieu/config_flow.py +++ b/homeassistant/components/twentemilieu/config_flow.py @@ -66,7 +66,8 @@ class TwenteMilieuFlowHandler(ConfigFlow, domain=DOMAIN): errors["base"] = "invalid_address" return await self._show_setup_form(errors) - self._async_abort_entries_match({CONF_ID: unique_id}) + await self.async_set_unique_id(str(unique_id)) + self._abort_if_unique_id_configured() return self.async_create_entry( title=str(unique_id), diff --git a/homeassistant/components/twentemilieu/const.py b/homeassistant/components/twentemilieu/const.py index 30f770efd25..95ab903cc17 100644 --- a/homeassistant/components/twentemilieu/const.py +++ b/homeassistant/components/twentemilieu/const.py @@ -1,8 +1,12 @@ """Constants for the Twente Milieu integration.""" +from datetime import timedelta +import logging +from typing import Final -DOMAIN = "twentemilieu" +DOMAIN: Final = "twentemilieu" -DATA_UPDATE = "twentemilieu_update" +LOGGER = logging.getLogger(__package__) +SCAN_INTERVAL = timedelta(hours=1) CONF_POST_CODE = "post_code" CONF_HOUSE_NUMBER = "house_number" diff --git a/homeassistant/components/twentemilieu/sensor.py b/homeassistant/components/twentemilieu/sensor.py index d13ae3a7165..04aa635b4a4 100644 --- a/homeassistant/components/twentemilieu/sensor.py +++ b/homeassistant/components/twentemilieu/sensor.py @@ -2,21 +2,23 @@ from __future__ import annotations from dataclasses import dataclass +from datetime import date -from twentemilieu import TwenteMilieu, TwenteMilieuConnectionError, WasteType +from twentemilieu import WasteType from homeassistant.components.sensor import SensorEntity, SensorEntityDescription from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ID, DEVICE_CLASS_DATE from homeassistant.core import HomeAssistant -from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, +) -from .const import DATA_UPDATE, DOMAIN - -PARALLEL_UPDATES = 1 +from .const import DOMAIN @dataclass @@ -71,55 +73,38 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up Twente Milieu sensor based on a config entry.""" - twentemilieu = hass.data[DOMAIN][entry.data[CONF_ID]] - - try: - await twentemilieu.update() - except TwenteMilieuConnectionError as exception: - raise PlatformNotReady from exception - + coordinator = hass.data[DOMAIN][entry.data[CONF_ID]] async_add_entities( - [ - TwenteMilieuSensor(twentemilieu, entry.data[CONF_ID], description) - for description in SENSORS - ], - True, + TwenteMilieuSensor(coordinator, description, entry) for description in SENSORS ) -class TwenteMilieuSensor(SensorEntity): +class TwenteMilieuSensor(CoordinatorEntity, SensorEntity): """Defines a Twente Milieu sensor.""" entity_description: TwenteMilieuSensorDescription - _attr_should_poll = False + coordinator: DataUpdateCoordinator[dict[WasteType, date | None]] def __init__( self, - twentemilieu: TwenteMilieu, - unique_id: str, + coordinator: DataUpdateCoordinator, description: TwenteMilieuSensorDescription, + entry: ConfigEntry, ) -> None: """Initialize the Twente Milieu entity.""" + super().__init__(coordinator=coordinator) self.entity_description = description - self._twentemilieu = twentemilieu - self._attr_unique_id = f"{DOMAIN}_{unique_id}_{description.key}" + self._attr_unique_id = f"{DOMAIN}_{entry.data[CONF_ID]}_{description.key}" self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, unique_id)}, + configuration_url="https://www.twentemilieu.nl", + identifiers={(DOMAIN, entry.data[CONF_ID])}, manufacturer="Twente Milieu", name="Twente Milieu", ) - async def async_added_to_hass(self) -> None: - """Connect to dispatcher listening for entity data notifications.""" - self.async_on_remove( - async_dispatcher_connect( - self.hass, DATA_UPDATE, self.async_schedule_update_ha_state - ) - ) - - async def async_update(self) -> None: - """Update Twente Milieu entity.""" - pickups = await self._twentemilieu.update() - self._attr_native_value = None - if pickup := pickups.get(self.entity_description.waste_type): - self._attr_native_value = pickup.isoformat() + @property + def native_value(self) -> StateType: + """Return the state of the sensor.""" + if pickup := self.coordinator.data.get(self.entity_description.waste_type): + return pickup.isoformat() + return None diff --git a/homeassistant/components/twentemilieu/services.yaml b/homeassistant/components/twentemilieu/services.yaml deleted file mode 100644 index 6227bad1b6d..00000000000 --- a/homeassistant/components/twentemilieu/services.yaml +++ /dev/null @@ -1,11 +0,0 @@ -update: - name: Update - description: Update all entities with fresh data from Twente Milieu - fields: - id: - name: ID - description: Specific unique address ID to update - advanced: true - example: 1300012345 - selector: - text: diff --git a/mypy.ini b/mypy.ini index f879ed5799b..4e374c8b1ae 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1474,6 +1474,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.twentemilieu.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.upcloud.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/tests/components/twentemilieu/test_config_flow.py b/tests/components/twentemilieu/test_config_flow.py index e4e4b0c8335..ebded3cffdd 100644 --- a/tests/components/twentemilieu/test_config_flow.py +++ b/tests/components/twentemilieu/test_config_flow.py @@ -74,7 +74,10 @@ async def test_address_already_set_up( ) -> None: """Test we abort if address has already been set up.""" MockConfigEntry( - domain=DOMAIN, data={**FIXTURE_USER_INPUT, CONF_ID: "12345"}, title="12345" + domain=DOMAIN, + data={**FIXTURE_USER_INPUT, CONF_ID: "12345"}, + title="12345", + unique_id="12345", ).add_to_hass(hass) aioclient_mock.post(