diff --git a/custom_components/dwd_rain_radar/config_flow.py b/custom_components/dwd_rain_radar/config_flow.py index 6fd6e0e..c85f811 100644 --- a/custom_components/dwd_rain_radar/config_flow.py +++ b/custom_components/dwd_rain_radar/config_flow.py @@ -23,6 +23,7 @@ class DwdRainRadarConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for the DWD Rain Radar coordinator.""" VERSION = 1 + MINOR_VERSION = 1 def __init__(self) -> None: """Initialize the config flow.""" @@ -56,7 +57,7 @@ async def async_step_user(self, user_input=None): return self.async_show_form( step_id="user", data_schema=vol.Schema({ - vol.Required(CONF_NAME, default="DWD Rain Radar", description="Name"): str, + vol.Required(CONF_NAME, default="DWD Radar", description="Name"): str, vol.Optional(CONF_COORDINATES, description="Location"): selector.LocationSelector( selector.LocationSelectorConfig() ) diff --git a/custom_components/dwd_rain_radar/const.py b/custom_components/dwd_rain_radar/const.py index af5cdfc..2675c98 100644 --- a/custom_components/dwd_rain_radar/const.py +++ b/custom_components/dwd_rain_radar/const.py @@ -5,6 +5,8 @@ DOMAIN = "dwd_rain_radar" +ATTRIBUTION = "Data provided by Deutscher Wetterdienst (DWD)" + PLATFORMS = [Platform.SENSOR] CONF_COORDINATES = "coordinates" diff --git a/custom_components/dwd_rain_radar/coordinator.py b/custom_components/dwd_rain_radar/coordinator.py index f2c60bd..47a7414 100644 --- a/custom_components/dwd_rain_radar/coordinator.py +++ b/custom_components/dwd_rain_radar/coordinator.py @@ -4,6 +4,7 @@ import logging from datetime import datetime, timedelta, timezone +import pandas as pd from dataclasses import dataclass from typing import List @@ -32,7 +33,7 @@ class PrecipitationForecast: def from_radolan_data(cls, data) -> PrecipitationForecast: """Return instance of Precipitation.""" return cls( - prediction_time=data.prediction_time.values[0], + prediction_time=pd.to_datetime(data.prediction_time.values[0]).to_pydatetime(), precipitation=data.RV.values.item() ) @@ -59,6 +60,7 @@ def __init__( self.lat = self.coords["latitude"] self.lon = self.coords["longitude"] self.radolan = Radolan(self.lat, self.lon, self.async_client) + self.latest_update = None async def _async_update_data(self) -> List[PrecipitationForecast]: """Update the data""" @@ -71,4 +73,6 @@ async def _async_update_data(self) -> List[PrecipitationForecast]: _LOGGER.debug("Fetched forecasts: {}".format(forecasts)) + self.latest_update = datetime.now() + return forecasts diff --git a/custom_components/dwd_rain_radar/sensor.py b/custom_components/dwd_rain_radar/sensor.py index b3280b6..8df1919 100644 --- a/custom_components/dwd_rain_radar/sensor.py +++ b/custom_components/dwd_rain_radar/sensor.py @@ -18,11 +18,13 @@ SensorStateClass, ) -from .const import DOMAIN +from .const import DOMAIN, ATTRIBUTION +from homeassistant.const import ( + ATTR_ATTRIBUTION +) from .coordinator import DwdRainRadarUpdateCoordinator, PrecipitationForecast from .entity import DwdCoordinatorEntity - _LOGGER = logging.getLogger(__name__) @@ -30,7 +32,8 @@ class PrecipitationSensorEntityDescription(SensorEntityDescription): """Provide a description for a precipitation sensor.""" - value_fn: Callable[[PrecipitationForecast], float | None] + value_fn: Callable[[PrecipitationForecast]] + extra_state_attributes_fn: Callable[[PrecipitationForecast], dict] = lambda _: {} exists_fn: Callable[[dict], bool] = lambda _: True @@ -41,7 +44,25 @@ class PrecipitationSensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=UnitOfPrecipitationDepth.MILLIMETERS, device_class=SensorDeviceClass.PRECIPITATION, state_class=SensorStateClass.MEASUREMENT, - value_fn=lambda forecast: forecast.precipitation, + value_fn=lambda forecasts: forecasts[0].precipitation, + extra_state_attributes_fn=lambda forecasts: { + 'prediction_time': forecasts[0].prediction_time + }, + ), + PrecipitationSensorEntityDescription( + key="rain_expected_at", + name="Rain Expected At", + device_class=SensorDeviceClass.DATE, + value_fn=lambda forecasts: next( + (forecast.prediction_time for forecast in forecasts if forecast.precipitation > 0), + None + ), + extra_state_attributes_fn=lambda forecasts: { + 'precipitation': next( + (forecast.precipitation for forecast in forecasts if forecast.precipitation > 0), + None + ) + }, ) ] @@ -72,14 +93,21 @@ def __init__( super().__init__(coordinator, description) self._attr_unique_id = ( - f"{self.coordinator.config_entry.entry_id}" - + f"_{self.entity_description.key}" + f"{self.coordinator.config_entry.entry_id}" + + f"_{self.entity_description.key}" ) @property def native_value(self): """Return the state of the sensor.""" - forecasts = self.coordinator.data - assert forecasts[0] is not None + return self.entity_description.value_fn(self.coordinator.data) + + @property + def extra_state_attributes(self): + """Return the state attributes of the device.""" + attributes = self.entity_description.extra_state_attributes_fn(self.coordinator.data) + + attributes['latest_update'] = self.coordinator.latest_update + attributes[ATTR_ATTRIBUTION] = ATTRIBUTION - return self.entity_description.value_fn(forecasts[0]) + return attributes diff --git a/tests/test_sensor.py b/tests/test_sensor.py index da9c220..fe0adcf 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -38,9 +38,15 @@ async def test_sensor(mock_get, hass, enable_custom_integrations): - state = hass.states.get("sensor.mock_title_precipitation") + precipitation = hass.states.get("sensor.mock_title_precipitation") - assert state - assert state.state == '0.839999973773956' - assert state.attributes['unit_of_measurement'] == 'mm' - assert state.attributes['device_class'] == 'precipitation' + assert precipitation + assert precipitation.state == '0.839999973773956' + assert precipitation.attributes['unit_of_measurement'] == 'mm' + assert precipitation.attributes['device_class'] == 'precipitation' + + + rain_expected_at = hass.states.get("sensor.mock_title_rain_expected_at") + + assert rain_expected_at + assert rain_expected_at.state == '2024-08-08T15:50:00'