forked from home-assistant/core
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add IMGW-PIB integration (home-assistant#116468)
* Add sensor platform * Add tests * Fix icons.json * Use entry.runtime_data * Remove validate_input function * Change abort reason to cannot_connect * Remove unnecessary square brackets * Move _attr_attribution outside the constructor * Use native_value property * Use is with ENUMs * Import SOURCE_USER * Change test name * Use freezer.tick * Tests refactoring * Remove test_setup_entry * Test creating entry after error * Add missing async_block_till_done * Fix translation key * Remove coordinator type annotation * Enable strict typing * Assert config entry unique_id --------- Co-authored-by: Maciej Bieniek <[email protected]>
- Loading branch information
Showing
21 changed files
with
877 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
"""The IMGW-PIB integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass | ||
import logging | ||
|
||
from aiohttp import ClientError | ||
from imgw_pib import ImgwPib | ||
from imgw_pib.exceptions import ApiError | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import Platform | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import ConfigEntryNotReady | ||
from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
|
||
from .const import CONF_STATION_ID | ||
from .coordinator import ImgwPibDataUpdateCoordinator | ||
|
||
PLATFORMS: list[Platform] = [Platform.SENSOR] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
ImgwPibConfigEntry = ConfigEntry["ImgwPibData"] | ||
|
||
|
||
@dataclass | ||
class ImgwPibData: | ||
"""Data for the IMGW-PIB integration.""" | ||
|
||
coordinator: ImgwPibDataUpdateCoordinator | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ImgwPibConfigEntry) -> bool: | ||
"""Set up IMGW-PIB from a config entry.""" | ||
station_id: str = entry.data[CONF_STATION_ID] | ||
|
||
_LOGGER.debug("Using hydrological station ID: %s", station_id) | ||
|
||
client_session = async_get_clientsession(hass) | ||
|
||
try: | ||
imgwpib = await ImgwPib.create( | ||
client_session, hydrological_station_id=station_id | ||
) | ||
except (ClientError, TimeoutError, ApiError) as err: | ||
raise ConfigEntryNotReady from err | ||
|
||
coordinator = ImgwPibDataUpdateCoordinator(hass, imgwpib, station_id) | ||
await coordinator.async_config_entry_first_refresh() | ||
|
||
entry.runtime_data = ImgwPibData(coordinator) | ||
|
||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ImgwPibConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
"""Config flow for IMGW-PIB integration.""" | ||
|
||
from __future__ import annotations | ||
|
||
import logging | ||
from typing import Any | ||
|
||
from aiohttp import ClientError | ||
from imgw_pib import ImgwPib | ||
from imgw_pib.exceptions import ApiError | ||
import voluptuous as vol | ||
|
||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult | ||
from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
from homeassistant.helpers.selector import ( | ||
SelectOptionDict, | ||
SelectSelector, | ||
SelectSelectorConfig, | ||
SelectSelectorMode, | ||
) | ||
|
||
from .const import CONF_STATION_ID, DOMAIN | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class ImgwPibFlowHandler(ConfigFlow, domain=DOMAIN): | ||
"""Handle a config flow for IMGW-PIB.""" | ||
|
||
VERSION = 1 | ||
|
||
async def async_step_user( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> ConfigFlowResult: | ||
"""Handle the initial step.""" | ||
errors: dict[str, str] = {} | ||
|
||
client_session = async_get_clientsession(self.hass) | ||
|
||
if user_input is not None: | ||
station_id = user_input[CONF_STATION_ID] | ||
|
||
await self.async_set_unique_id(station_id, raise_on_progress=False) | ||
self._abort_if_unique_id_configured() | ||
|
||
try: | ||
imgwpib = await ImgwPib.create( | ||
client_session, hydrological_station_id=station_id | ||
) | ||
hydrological_data = await imgwpib.get_hydrological_data() | ||
except (ClientError, TimeoutError, ApiError): | ||
errors["base"] = "cannot_connect" | ||
except Exception: # pylint: disable=broad-except | ||
_LOGGER.exception("Unexpected exception") | ||
errors["base"] = "unknown" | ||
else: | ||
title = f"{hydrological_data.river} ({hydrological_data.station})" | ||
return self.async_create_entry(title=title, data=user_input) | ||
|
||
try: | ||
imgwpib = await ImgwPib.create(client_session) | ||
await imgwpib.update_hydrological_stations() | ||
except (ClientError, TimeoutError, ApiError): | ||
return self.async_abort(reason="cannot_connect") | ||
|
||
options: list[SelectOptionDict] = [ | ||
SelectOptionDict(value=station_id, label=station_name) | ||
for station_id, station_name in imgwpib.hydrological_stations.items() | ||
] | ||
|
||
schema: vol.Schema = vol.Schema( | ||
{ | ||
vol.Required(CONF_STATION_ID): SelectSelector( | ||
SelectSelectorConfig( | ||
options=options, | ||
multiple=False, | ||
sort=True, | ||
mode=SelectSelectorMode.DROPDOWN, | ||
), | ||
) | ||
} | ||
) | ||
|
||
return self.async_show_form(step_id="user", data_schema=schema, errors=errors) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
"""Constants for the IMGW-PIB integration.""" | ||
|
||
from datetime import timedelta | ||
|
||
DOMAIN = "imgw_pib" | ||
|
||
ATTRIBUTION = "Data provided by IMGW-PIB" | ||
|
||
CONF_STATION_ID = "station_id" | ||
|
||
UPDATE_INTERVAL = timedelta(minutes=30) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
"""Data Update Coordinator for IMGW-PIB integration.""" | ||
|
||
import logging | ||
|
||
from imgw_pib import ApiError, HydrologicalData, ImgwPib | ||
|
||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo | ||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed | ||
|
||
from .const import DOMAIN, UPDATE_INTERVAL | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class ImgwPibDataUpdateCoordinator(DataUpdateCoordinator[HydrologicalData]): | ||
"""Class to manage fetching IMGW-PIB data API.""" | ||
|
||
def __init__( | ||
self, | ||
hass: HomeAssistant, | ||
imgwpib: ImgwPib, | ||
station_id: str, | ||
) -> None: | ||
"""Initialize.""" | ||
self.imgwpib = imgwpib | ||
self.station_id = station_id | ||
self.device_info = DeviceInfo( | ||
entry_type=DeviceEntryType.SERVICE, | ||
identifiers={(DOMAIN, station_id)}, | ||
manufacturer="IMGW-PIB", | ||
name=f"{imgwpib.hydrological_stations[station_id]}", | ||
configuration_url=f"https://hydro.imgw.pl/#/station/hydro/{station_id}", | ||
) | ||
|
||
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=UPDATE_INTERVAL) | ||
|
||
async def _async_update_data(self) -> HydrologicalData: | ||
"""Update data via internal method.""" | ||
try: | ||
return await self.imgwpib.get_hydrological_data() | ||
except ApiError as err: | ||
raise UpdateFailed(err) from err |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"entity": { | ||
"sensor": { | ||
"water_level": { | ||
"default": "mdi:waves" | ||
}, | ||
"water_temperature": { | ||
"default": "mdi:thermometer-water" | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"domain": "imgw_pib", | ||
"name": "IMGW-PIB", | ||
"codeowners": ["@bieniu"], | ||
"config_flow": true, | ||
"documentation": "https://www.home-assistant.io/integrations/imgw_pib", | ||
"iot_class": "cloud_polling", | ||
"requirements": ["imgw_pib==1.0.0"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
"""IMGW-PIB sensor platform.""" | ||
|
||
from __future__ import annotations | ||
|
||
from collections.abc import Callable | ||
from dataclasses import dataclass | ||
|
||
from imgw_pib.model import HydrologicalData | ||
|
||
from homeassistant.components.sensor import ( | ||
SensorDeviceClass, | ||
SensorEntity, | ||
SensorEntityDescription, | ||
SensorStateClass, | ||
) | ||
from homeassistant.const import UnitOfLength, UnitOfTemperature | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.entity_platform import AddEntitiesCallback | ||
from homeassistant.helpers.typing import StateType | ||
from homeassistant.helpers.update_coordinator import CoordinatorEntity | ||
|
||
from . import ImgwPibConfigEntry | ||
from .const import ATTRIBUTION | ||
from .coordinator import ImgwPibDataUpdateCoordinator | ||
|
||
PARALLEL_UPDATES = 1 | ||
|
||
|
||
@dataclass(frozen=True, kw_only=True) | ||
class ImgwPibSensorEntityDescription(SensorEntityDescription): | ||
"""IMGW-PIB sensor entity description.""" | ||
|
||
value: Callable[[HydrologicalData], StateType] | ||
|
||
|
||
SENSOR_TYPES: tuple[ImgwPibSensorEntityDescription, ...] = ( | ||
ImgwPibSensorEntityDescription( | ||
key="water_level", | ||
translation_key="water_level", | ||
native_unit_of_measurement=UnitOfLength.CENTIMETERS, | ||
device_class=SensorDeviceClass.DISTANCE, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
suggested_display_precision=0, | ||
value=lambda data: data.water_level.value, | ||
), | ||
ImgwPibSensorEntityDescription( | ||
key="water_temperature", | ||
translation_key="water_temperature", | ||
native_unit_of_measurement=UnitOfTemperature.CELSIUS, | ||
device_class=SensorDeviceClass.TEMPERATURE, | ||
state_class=SensorStateClass.MEASUREMENT, | ||
suggested_display_precision=1, | ||
value=lambda data: data.water_temperature.value, | ||
), | ||
) | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, | ||
entry: ImgwPibConfigEntry, | ||
async_add_entities: AddEntitiesCallback, | ||
) -> None: | ||
"""Add a IMGW-PIB sensor entity from a config_entry.""" | ||
coordinator = entry.runtime_data.coordinator | ||
|
||
async_add_entities( | ||
ImgwPibSensorEntity(coordinator, description) | ||
for description in SENSOR_TYPES | ||
if getattr(coordinator.data, description.key).value is not None | ||
) | ||
|
||
|
||
class ImgwPibSensorEntity( | ||
CoordinatorEntity[ImgwPibDataUpdateCoordinator], SensorEntity | ||
): | ||
"""Define IMGW-PIB sensor entity.""" | ||
|
||
_attr_attribution = ATTRIBUTION | ||
_attr_has_entity_name = True | ||
entity_description: ImgwPibSensorEntityDescription | ||
|
||
def __init__( | ||
self, | ||
coordinator: ImgwPibDataUpdateCoordinator, | ||
description: ImgwPibSensorEntityDescription, | ||
) -> None: | ||
"""Initialize.""" | ||
super().__init__(coordinator) | ||
|
||
self._attr_unique_id = f"{coordinator.station_id}_{description.key}" | ||
self._attr_device_info = coordinator.device_info | ||
self.entity_description = description | ||
|
||
@property | ||
def native_value(self) -> StateType: | ||
"""Return the value reported by the sensor.""" | ||
return self.entity_description.value(self.coordinator.data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
{ | ||
"config": { | ||
"step": { | ||
"user": { | ||
"data": { | ||
"station_id": "Hydrological station" | ||
} | ||
} | ||
}, | ||
"error": { | ||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", | ||
"unknown": "[%key:common::config_flow::error::unknown%]" | ||
}, | ||
"abort": { | ||
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]", | ||
"cannot_connect": "Failed to connect" | ||
} | ||
}, | ||
"entity": { | ||
"sensor": { | ||
"water_level": { | ||
"name": "Water level" | ||
}, | ||
"water_temperature": { | ||
"name": "Water temperature" | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -251,6 +251,7 @@ | |
"idasen_desk", | ||
"ifttt", | ||
"imap", | ||
"imgw_pib", | ||
"improv_ble", | ||
"inkbird", | ||
"insteon", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.