Compare commits
No commits in common. "32e9c3af4dee57904abc6ff9ed6307d2ff13e6cf" and "8cd5ae5283f5d422f9e2351f71bed85131891f5c" have entirely different histories.
32e9c3af4d
...
8cd5ae5283
@ -1 +1 @@
|
|||||||
2024.10.1
|
2024.10.0
|
@ -1,76 +0,0 @@
|
|||||||
"""The Länderübergreifendes Hochwasser Portal integration."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from lhpapi import HochwasserPortalAPI, LHPError
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
|
|
||||||
from .const import (
|
|
||||||
CONF_ADD_UNAVAILABLE,
|
|
||||||
CONF_PEGEL_IDENTIFIER,
|
|
||||||
DOMAIN,
|
|
||||||
LOGGER,
|
|
||||||
PLATFORMS,
|
|
||||||
)
|
|
||||||
from .coordinator import HochwasserPortalCoordinator
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
||||||
"""Set up a config entry."""
|
|
||||||
pegel_identifier: str = entry.data[CONF_PEGEL_IDENTIFIER]
|
|
||||||
|
|
||||||
# Initialize the API and coordinator.
|
|
||||||
try:
|
|
||||||
api = await hass.async_add_executor_job(HochwasserPortalAPI, pegel_identifier)
|
|
||||||
coordinator = HochwasserPortalCoordinator(hass, api)
|
|
||||||
except LHPError as err:
|
|
||||||
LOGGER.exception("Setup of %s failed: %s", pegel_identifier, err)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# No need to refresh via the following line because api runs
|
|
||||||
# update during init automatically
|
|
||||||
# await coordinator.async_config_entry_first_refresh()
|
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
|
||||||
"""Migrate old entry."""
|
|
||||||
LOGGER.debug(
|
|
||||||
"Migrating %s from version %s.%s",
|
|
||||||
config_entry.title,
|
|
||||||
config_entry.version,
|
|
||||||
config_entry.minor_version,
|
|
||||||
)
|
|
||||||
|
|
||||||
if config_entry.version > 1:
|
|
||||||
# This means the user has downgraded from a future version
|
|
||||||
return False
|
|
||||||
|
|
||||||
if config_entry.version == 1:
|
|
||||||
new = {**config_entry.data}
|
|
||||||
if config_entry.minor_version < 2:
|
|
||||||
new[CONF_ADD_UNAVAILABLE] = True # Behaviour as in 1.1
|
|
||||||
config_entry.minor_version = 2
|
|
||||||
hass.config_entries.async_update_entry(config_entry, data=new)
|
|
||||||
|
|
||||||
LOGGER.debug(
|
|
||||||
"Migration of %s to version %s.%s successful",
|
|
||||||
config_entry.title,
|
|
||||||
config_entry.version,
|
|
||||||
config_entry.minor_version,
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
||||||
"""Unload a config entry."""
|
|
||||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
|
||||||
hass.data[DOMAIN].pop(entry.entry_id)
|
|
||||||
|
|
||||||
return unload_ok
|
|
@ -1,62 +0,0 @@
|
|||||||
"""Config flow for the hochwasserportal integration."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from lhpapi import HochwasserPortalAPI, LHPError
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigFlow
|
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
|
|
||||||
from .const import CONF_ADD_UNAVAILABLE, CONF_PEGEL_IDENTIFIER, DOMAIN, LOGGER
|
|
||||||
|
|
||||||
|
|
||||||
class HochwasserPortalConfigFlow(ConfigFlow, domain=DOMAIN):
|
|
||||||
"""Handle the config flow for the hochwasserportal integration."""
|
|
||||||
|
|
||||||
VERSION = 1
|
|
||||||
MINOR_VERSION = 2
|
|
||||||
|
|
||||||
async def async_step_user(
|
|
||||||
self, user_input: dict[str, Any] | None = None
|
|
||||||
) -> FlowResult:
|
|
||||||
"""Handle the initial step."""
|
|
||||||
errors: dict = {}
|
|
||||||
|
|
||||||
if user_input is not None:
|
|
||||||
pegel_identifier = user_input[CONF_PEGEL_IDENTIFIER]
|
|
||||||
|
|
||||||
# Validate pegel identifier using the API
|
|
||||||
try:
|
|
||||||
api = await self.hass.async_add_executor_job(
|
|
||||||
HochwasserPortalAPI, pegel_identifier
|
|
||||||
)
|
|
||||||
LOGGER.debug(
|
|
||||||
"%s (%s): Successfully added!",
|
|
||||||
api.ident,
|
|
||||||
api.name,
|
|
||||||
)
|
|
||||||
except LHPError as err:
|
|
||||||
LOGGER.exception("Setup of %s failed: %s", pegel_identifier, err)
|
|
||||||
errors["base"] = "invalid_identifier"
|
|
||||||
|
|
||||||
if not errors:
|
|
||||||
# Set the unique ID for this config entry.
|
|
||||||
await self.async_set_unique_id(f"{DOMAIN}_{pegel_identifier.lower()}")
|
|
||||||
self._abort_if_unique_id_configured()
|
|
||||||
|
|
||||||
return self.async_create_entry(title=f"{api.name}", data=user_input)
|
|
||||||
|
|
||||||
return self.async_show_form(
|
|
||||||
step_id="user",
|
|
||||||
errors=errors,
|
|
||||||
data_schema=vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Required(CONF_PEGEL_IDENTIFIER): cv.string,
|
|
||||||
vol.Required(CONF_ADD_UNAVAILABLE, default=False): cv.boolean,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
@ -1,46 +0,0 @@
|
|||||||
"""Constants for the Länderübergreifendes Hochwasser Portal integration."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
|
||||||
from typing import Final
|
|
||||||
|
|
||||||
from homeassistant.const import Platform
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__package__)
|
|
||||||
|
|
||||||
DOMAIN: Final = "hochwasserportal"
|
|
||||||
|
|
||||||
CONF_PEGEL_IDENTIFIER: Final = "pegel_identifier"
|
|
||||||
CONF_ADD_UNAVAILABLE: Final = "add_unavailable"
|
|
||||||
|
|
||||||
ATTR_DATA_PROVIDERS: Final[dict[str, str]] = {
|
|
||||||
"BB": "LfU Brandenburg",
|
|
||||||
"BE": "SenMVKU Berlin",
|
|
||||||
"BW": "LUBW Baden-Württemberg",
|
|
||||||
"BY": "LfU Bayern",
|
|
||||||
"HB": "SUKW Bremen",
|
|
||||||
"HE": "HLNUG",
|
|
||||||
"HH": "LSBG Hamburg",
|
|
||||||
"MV": "LUNG Mecklenburg-Vorpommern",
|
|
||||||
"NI": "NLWKN",
|
|
||||||
"NW": "LANUV Nordrhein-Westfalen",
|
|
||||||
"RP": "Luf Rheinland-Pfalz",
|
|
||||||
"SH": "Luf Schleswig-Holstein",
|
|
||||||
"SL": "LUA Saarland",
|
|
||||||
"SN": "LfULG Sachsen",
|
|
||||||
"ST": "Land Sachsen-Anhalt",
|
|
||||||
"TH": "TLUBN",
|
|
||||||
}
|
|
||||||
ATTR_LAST_UPDATE: Final = "last_update"
|
|
||||||
ATTR_URL: Final = "url"
|
|
||||||
ATTR_HINT: Final = "hint"
|
|
||||||
|
|
||||||
LEVEL_SENSOR: Final = "level"
|
|
||||||
STAGE_SENSOR: Final = "stage"
|
|
||||||
FLOW_SENSOR: Final = "flow"
|
|
||||||
|
|
||||||
DEFAULT_SCAN_INTERVAL: Final = timedelta(minutes=15)
|
|
||||||
|
|
||||||
PLATFORMS: Final[list[Platform]] = [Platform.SENSOR]
|
|
@ -1,32 +0,0 @@
|
|||||||
"""Data coordinator for the hochwasserportal integration."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from lhpapi import HochwasserPortalAPI, LHPError
|
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
|
||||||
|
|
||||||
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER
|
|
||||||
|
|
||||||
|
|
||||||
class HochwasserPortalCoordinator(DataUpdateCoordinator[None]):
|
|
||||||
"""Custom coordinator for the hochwasserportal integration."""
|
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, api: HochwasserPortalAPI) -> None:
|
|
||||||
"""Initialize the hochwasserportal coordinator."""
|
|
||||||
super().__init__(
|
|
||||||
hass, LOGGER, name=DOMAIN, update_interval=DEFAULT_SCAN_INTERVAL
|
|
||||||
)
|
|
||||||
|
|
||||||
self.api = api
|
|
||||||
LOGGER.debug("%s", repr(self.api))
|
|
||||||
|
|
||||||
async def _async_update_data(self) -> None:
|
|
||||||
"""Get the latest data from the hochwasserportal API."""
|
|
||||||
try:
|
|
||||||
await self.hass.async_add_executor_job(self.api.update)
|
|
||||||
LOGGER.debug("%s", repr(self.api))
|
|
||||||
except LHPError as err:
|
|
||||||
LOGGER.exception("Update of %s failed: %s", self.api.ident, err)
|
|
||||||
return False
|
|
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"domain": "hochwasserportal",
|
|
||||||
"name": "Länderübergreifendes Hochwasser Portal",
|
|
||||||
"codeowners": ["@stephan192"],
|
|
||||||
"config_flow": true,
|
|
||||||
"dependencies": [],
|
|
||||||
"documentation": "https://github.com/stephan192/hochwasserportal",
|
|
||||||
"integration_type": "device",
|
|
||||||
"iot_class": "cloud_polling",
|
|
||||||
"issue_tracker": "https://github.com/stephan192/hochwasserportal/issues",
|
|
||||||
"loggers": ["hochwasserportal"],
|
|
||||||
"requirements": ["lhpapi==1.0.3"],
|
|
||||||
"version": "1.0.1"
|
|
||||||
}
|
|
@ -1,148 +0,0 @@
|
|||||||
"""Platform for sensor integration."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from collections.abc import Callable
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from lhpapi import HochwasserPortalAPI
|
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
|
||||||
SensorEntity,
|
|
||||||
SensorEntityDescription,
|
|
||||||
SensorStateClass,
|
|
||||||
)
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
|
||||||
from homeassistant.const import UnitOfLength
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
|
||||||
|
|
||||||
from .const import (
|
|
||||||
ATTR_DATA_PROVIDERS,
|
|
||||||
ATTR_HINT,
|
|
||||||
ATTR_LAST_UPDATE,
|
|
||||||
ATTR_URL,
|
|
||||||
CONF_ADD_UNAVAILABLE,
|
|
||||||
DOMAIN,
|
|
||||||
FLOW_SENSOR,
|
|
||||||
LEVEL_SENSOR,
|
|
||||||
LOGGER,
|
|
||||||
STAGE_SENSOR,
|
|
||||||
)
|
|
||||||
from .coordinator import HochwasserPortalCoordinator
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
|
||||||
class HochwasserPortalSensorEntityDescription(SensorEntityDescription):
|
|
||||||
"""Describes HochwasserPortal sensor entity."""
|
|
||||||
|
|
||||||
value_fn: Callable[[HochwasserPortalAPI], int | float | None]
|
|
||||||
available_fn: Callable[[HochwasserPortalAPI], bool]
|
|
||||||
|
|
||||||
|
|
||||||
SENSOR_TYPES: tuple[HochwasserPortalSensorEntityDescription, ...] = (
|
|
||||||
HochwasserPortalSensorEntityDescription(
|
|
||||||
key=LEVEL_SENSOR,
|
|
||||||
translation_key=LEVEL_SENSOR,
|
|
||||||
icon="mdi:waves",
|
|
||||||
native_unit_of_measurement=UnitOfLength.CENTIMETERS,
|
|
||||||
device_class=None,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
value_fn=lambda api: api.level,
|
|
||||||
available_fn=lambda api: api.level is not None,
|
|
||||||
),
|
|
||||||
HochwasserPortalSensorEntityDescription(
|
|
||||||
key=STAGE_SENSOR,
|
|
||||||
translation_key=STAGE_SENSOR,
|
|
||||||
icon="mdi:waves-arrow-up",
|
|
||||||
native_unit_of_measurement=None,
|
|
||||||
device_class=None,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
value_fn=lambda api: api.stage,
|
|
||||||
available_fn=lambda api: api.stage is not None,
|
|
||||||
),
|
|
||||||
HochwasserPortalSensorEntityDescription(
|
|
||||||
key=FLOW_SENSOR,
|
|
||||||
translation_key=FLOW_SENSOR,
|
|
||||||
icon="mdi:waves-arrow-right",
|
|
||||||
native_unit_of_measurement="m³/s",
|
|
||||||
device_class=None,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
value_fn=lambda api: api.flow,
|
|
||||||
available_fn=lambda api: api.flow is not None,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
|
||||||
) -> None:
|
|
||||||
"""Set up entities from config entry."""
|
|
||||||
coordinator: HochwasserPortalCoordinator = hass.data[DOMAIN][entry.entry_id]
|
|
||||||
|
|
||||||
async_add_entities(
|
|
||||||
[
|
|
||||||
HochwasserPortalSensor(coordinator, entry, description)
|
|
||||||
for description in SENSOR_TYPES
|
|
||||||
if description.available_fn(coordinator.api)
|
|
||||||
or entry.data.get(CONF_ADD_UNAVAILABLE, False)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class HochwasserPortalSensor(
|
|
||||||
CoordinatorEntity[HochwasserPortalCoordinator], SensorEntity
|
|
||||||
):
|
|
||||||
"""Sensor representation."""
|
|
||||||
|
|
||||||
entity_description: HochwasserPortalSensorEntityDescription
|
|
||||||
_attr_has_entity_name = True
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
coordinator: HochwasserPortalCoordinator,
|
|
||||||
entry: ConfigEntry,
|
|
||||||
description: HochwasserPortalSensorEntityDescription,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the sensor."""
|
|
||||||
super().__init__(coordinator)
|
|
||||||
self.api = coordinator.api
|
|
||||||
self.entity_description = description
|
|
||||||
self._attr_unique_id = f"{entry.unique_id}_{description.key}"
|
|
||||||
self._attr_device_info = DeviceInfo(
|
|
||||||
identifiers={(DOMAIN, entry.entry_id)},
|
|
||||||
name=f"{entry.title}",
|
|
||||||
configuration_url=self.api.url,
|
|
||||||
manufacturer=f"{ATTR_DATA_PROVIDERS[self.api.ident[:2]]}",
|
|
||||||
model=f"{self.api.ident}",
|
|
||||||
)
|
|
||||||
self._attr_attribution = (
|
|
||||||
f"Data provided by {ATTR_DATA_PROVIDERS[self.api.ident[:2]]}"
|
|
||||||
)
|
|
||||||
LOGGER.debug("Setting up sensor: %s", self._attr_unique_id)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def native_value(self):
|
|
||||||
"""Return the state of the sensor."""
|
|
||||||
return self.entity_description.value_fn(self.api)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extra_state_attributes(self):
|
|
||||||
"""Return the state attributes of the sensor."""
|
|
||||||
data = {}
|
|
||||||
if self.api.last_update is not None:
|
|
||||||
data[ATTR_LAST_UPDATE] = self.api.last_update
|
|
||||||
if self.api.url is not None:
|
|
||||||
data[ATTR_URL] = self.api.url
|
|
||||||
if self.api.hint is not None:
|
|
||||||
data[ATTR_HINT] = self.api.hint
|
|
||||||
if bool(data):
|
|
||||||
return data
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self) -> bool:
|
|
||||||
"""Could the device be accessed during the last update call."""
|
|
||||||
return self.entity_description.available_fn(self.api)
|
|
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"step": {
|
|
||||||
"user": {
|
|
||||||
"description": "To identify the pegel, the pegel ID is required.",
|
|
||||||
"data": {
|
|
||||||
"pegel_identifier": "Pegel ID",
|
|
||||||
"add_unavailable": "Add unavailable entities anyway"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"invalid_identifier": "The specified pegel identifier is invalid."
|
|
||||||
},
|
|
||||||
"abort": {
|
|
||||||
"already_configured": "Pegel is already configured.",
|
|
||||||
"invalid_identifier": "[%key:component::hochwasserportal::config::error::invalid_identifier%]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"entity": {
|
|
||||||
"sensor": {
|
|
||||||
"level": {
|
|
||||||
"name": "Level"
|
|
||||||
},
|
|
||||||
"stage": {
|
|
||||||
"name": "Stage"
|
|
||||||
},
|
|
||||||
"flow": {
|
|
||||||
"name": "Flow"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"step": {
|
|
||||||
"user": {
|
|
||||||
"description": "Um den Pegel zu identifizieren, ist die Pegel-ID erforderlich.",
|
|
||||||
"data": {
|
|
||||||
"pegel_identifier": "Pegel ID",
|
|
||||||
"add_unavailable": "Nicht verfügbare Entitäten trotzdem hinzufügen"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"invalid_identifier": "Der angegebene Pegel ist ungültig."
|
|
||||||
},
|
|
||||||
"abort": {
|
|
||||||
"already_configured": "Pegel bereits konfiguriert.",
|
|
||||||
"invalid_identifier": "[%key:component::hochwasserportal::config::error::invalid_identifier%]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"entity": {
|
|
||||||
"sensor": {
|
|
||||||
"level": {
|
|
||||||
"name": "Pegelstand"
|
|
||||||
},
|
|
||||||
"stage": {
|
|
||||||
"name": "Warnstufe"
|
|
||||||
},
|
|
||||||
"flow": {
|
|
||||||
"name": "Abfluss"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"config": {
|
|
||||||
"step": {
|
|
||||||
"user": {
|
|
||||||
"description": "To identify the pegel, the pegel ID is required.",
|
|
||||||
"data": {
|
|
||||||
"pegel_identifier": "Pegel ID",
|
|
||||||
"add_unavailable": "Add unavailable entities anyway"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"invalid_identifier": "The specified pegel identifier is invalid."
|
|
||||||
},
|
|
||||||
"abort": {
|
|
||||||
"already_configured": "Pegel is already configured.",
|
|
||||||
"invalid_identifier": "[%key:component::hochwasserportal::config::error::invalid_identifier%]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"entity": {
|
|
||||||
"sensor": {
|
|
||||||
"level": {
|
|
||||||
"name": "Level"
|
|
||||||
},
|
|
||||||
"stage": {
|
|
||||||
"name": "Stage"
|
|
||||||
},
|
|
||||||
"flow": {
|
|
||||||
"name": "Flow"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user