Skip to content

Commit

Permalink
Update to new HA forecast format
Browse files Browse the repository at this point in the history
  • Loading branch information
FL550 committed Sep 18, 2023
1 parent 5866750 commit 9bddb1d
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 93 deletions.
3 changes: 1 addition & 2 deletions custom_components/dwd_weather/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from .connector import DWDWeatherData
from .const import (
CONF_STATION_ID,
CONF_WEATHER_INTERVAL,
CONF_WIND_DIRECTION_TYPE,
DEFAULT_SCAN_INTERVAL,
DEFAULT_WIND_DIRECTION_TYPE,
Expand Down Expand Up @@ -74,7 +73,7 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry):

if config_entry.version == 1:
new = {**config_entry.data}
new[CONF_WEATHER_INTERVAL] = 24
new["weather_interval"] = 24
config_entry.data = {**new}
config_entry.version = 2

Expand Down
54 changes: 11 additions & 43 deletions custom_components/dwd_weather/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
CONF_STATION_ID,
CONF_STATION_NAME,
DOMAIN,
CONF_WEATHER_INTERVAL,
CONF_WIND_DIRECTION_TYPE,
)

Expand Down Expand Up @@ -53,7 +52,7 @@ async def async_step_user(self, user_input=None):
elif user_input[CONF_ENTITY_TYPE] == "weather_map":
# Show map config form
return self.async_create_entry(
title="Weather Map TEst", data=self.config_data
title="Weather Map Test", data=self.config_data
)
pass

Expand All @@ -65,7 +64,7 @@ async def async_step_user(self, user_input=None):
): selector(
{
"select": {
"options": list(["weather_station", "weather_map"]),
"options": list(["weather_station"]), # , "weather_map"
"custom_value": False,
"mode": "list",
"translation_key": CONF_ENTITY_TYPE,
Expand All @@ -86,10 +85,12 @@ async def async_step_station_select(self, user_input=None):
station = dwdforecast.load_station_id(user_input["station_id"])
_LOGGER.debug("Station:validation: {}".format(station))
if station is not None:
self.config_data.update(user_input)
if station["report_available"] == 1:
self.config_data.update(user_input)
return await self.async_step_station_configure_report()
else:
self.config_data[CONF_DATA_TYPE] = "forecast_data"
self.config_data.update(user_input)
return await self.async_step_station_configure()
else:
errors = {"base": "invalid_station_id"}
Expand Down Expand Up @@ -168,7 +169,12 @@ async def async_step_station_configure(self, user_input=None):
)
if user_input is not None:
self.config_data.update(user_input)
return await self.async_step_granularity()
await self.async_set_unique_id(self.config_data[CONF_STATION_ID])
self._abort_if_unique_id_configured()
# The data is the data which is picked up by the async_setup_entry in sensor or weather
return self.async_create_entry(
title=self.config_data[CONF_STATION_ID], data=self.config_data
)

_LOGGER.debug(
"Station_configure:station_data: {}".format(
Expand Down Expand Up @@ -206,41 +212,3 @@ async def async_step_station_configure(self, user_input=None):
return self.async_show_form(
step_id="station_configure", data_schema=data_schema, errors=errors
)

async def async_step_granularity(self, user_input=None):
errors = {}
_LOGGER.debug(
"granularity:config: {},user_input: {}".format(self.config_data, user_input)
)
if user_input is not None:
if len(user_input[CONF_WEATHER_INTERVAL]) == 0:
errors = {"base": "invalid_weather_interval"}
else:
self.config_data.update(user_input)

await self.async_set_unique_id(self.config_data[CONF_STATION_ID])
self._abort_if_unique_id_configured()
# The data is the data which is picked up by the async_setup_entry in sensor or weather
return self.async_create_entry(
title=self.config_data[CONF_STATION_ID], data=self.config_data
)

data_schema = vol.Schema(
{
vol.Required(CONF_WEATHER_INTERVAL): selector(
{
"select": {
"options": list(["24", "12", "6", "3", "2", "1"]),
"custom_value": False,
"mode": "list",
"multiple": True,
"translation_key": CONF_WEATHER_INTERVAL,
}
}
),
}
)

return self.async_show_form(
step_id="granularity", data_schema=data_schema, errors=errors
)
14 changes: 11 additions & 3 deletions custom_components/dwd_weather/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
ATTR_FORECAST_TIME,
ATTR_FORECAST_WIND_BEARING,
ATTR_FORECAST_NATIVE_WIND_SPEED,
WeatherEntityFeature,
Forecast,
)
from simple_dwd_weatherforecast import dwdforecast
from simple_dwd_weatherforecast.dwdforecast import WeatherDataType
Expand Down Expand Up @@ -58,7 +60,9 @@ def _update(self):
self.dwd_weather.update(
force_hourly=False,
with_forecast=True,
with_measurements=True if self._config[CONF_DATA_TYPE] == "report_data" else False,
with_measurements=True
if self._config[CONF_DATA_TYPE] == "report_data"
else False,
with_report=True,
)
_LOGGER.info("Updating {}".format(self._config[CONF_STATION_NAME]))
Expand All @@ -74,7 +78,11 @@ def _update(self):
# )
# )

def get_forecast(self, weather_interval):
def get_forecast(self, WeatherEntityFeature_FORECAST) -> list[Forecast] | None:
if WeatherEntityFeature_FORECAST == WeatherEntityFeature.FORECAST_HOURLY:
weather_interval = 1
elif WeatherEntityFeature_FORECAST == WeatherEntityFeature.FORECAST_DAILY:
weather_interval = 24
timestep = datetime(
self.latest_update.year,
self.latest_update.month,
Expand Down Expand Up @@ -172,7 +180,7 @@ def get_weather_report(self):
return markdownify(self.dwd_weather.get_weather_report(), strip=["br"])

def get_weather_value(self, data_type: WeatherDataType):
#TODO True if self._config[CONF_DATA_TYPE] == "report_data" else False
# TODO True if self._config[CONF_DATA_TYPE] == "report_data" else False
value = self.dwd_weather.get_forecast_data(
data_type,
datetime.now(timezone.utc),
Expand Down
1 change: 0 additions & 1 deletion custom_components/dwd_weather/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,3 @@
CONF_STATION_NAME = "station_name"
CONF_WIND_DIRECTION_TYPE = "wind_direction_type"
CONF_HOURLY_UPDATE = "hourly_update"
CONF_WEATHER_INTERVAL = "weather_interval"
25 changes: 10 additions & 15 deletions custom_components/dwd_weather/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
ATTRIBUTION,
CONF_STATION_ID,
CONF_STATION_NAME,
CONF_WEATHER_INTERVAL,
DOMAIN,
DWDWEATHER_DATA,
)
Expand Down Expand Up @@ -194,29 +193,25 @@ async def async_setup_entry(
_LOGGER.debug("Sensor async_setup_entry {}".format(entry.data))
if CONF_STATION_ID in entry.data:
_LOGGER.debug("Sensor async_setup_entry")
for interval in entry.data[CONF_WEATHER_INTERVAL]:
async_add_entities(
[
DWDWeatherForecastSensor(
entry.data, hass_data, sensor_type, interval
)
for sensor_type in SENSOR_TYPES
],
False,
)
async_add_entities(
[
DWDWeatherForecastSensor(entry.data, hass_data, sensor_type)
for sensor_type in SENSOR_TYPES
],
False,
)


class DWDWeatherForecastSensor(DWDWeatherEntity, SensorEntity):
"""Implementation of a DWD current weather condition sensor."""

def __init__(self, entry_data, hass_data, sensor_type, weather_interval):
def __init__(self, entry_data, hass_data, sensor_type):
"""Initialize the sensor."""
dwd_data: DWDWeatherData = hass_data[DWDWEATHER_DATA]
self._type = sensor_type
self._weather_interval = int(weather_interval)

name = f"{dwd_data._config[CONF_STATION_NAME]}: {SENSOR_TYPES[self._type][0]}{' ' + str(weather_interval) + 'h' if weather_interval != '24' else ''}"
unique_id = f"{dwd_data._config[CONF_STATION_ID]}_{SENSOR_TYPES[self._type][0]}{'_' + str(weather_interval) if weather_interval != '24' else ''}"
name = f"{dwd_data._config[CONF_STATION_NAME]}: {SENSOR_TYPES[self._type][0]}"
unique_id = f"{dwd_data._config[CONF_STATION_ID]}_{SENSOR_TYPES[self._type][0]}"
_LOGGER.debug(
"Setting up sensor with id {} and name {}".format(unique_id, name)
)
Expand Down
79 changes: 67 additions & 12 deletions custom_components/dwd_weather/translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,81 @@
"config": {
"step": {
"user": {
"description": "Der Längen- und Breitengrad wird genutzt um die nächste Wetterstation zu ermitteln. Wenn eine Stations-ID angegeben wird, werden die Längen- und Breitengrad Angaben ignoriert. Verfügbare Stations-Ids können unter folgendem Link eingesehen werden: https://github.com/FL550/simple_dwd_weatherforecast/blob/master/simple_dwd_weatherforecast/stations.py",
"description": "Im Moment gibt es nur Wetterstationen, aber es kommen weitere Typen. Es können beliebig viele Stationen hinzugefügt werden. Die einzige Einschränkung ist, dass jede Station nur einmal eingerichtet sein darf.",
"title": "Deutscher Wetterdienst",
"data": {
"latitude": "Längengrad",
"longitude": "Breitengrad",
"station_id": "Stations-ID",
"weather_interval": "Vorhersage Interval in Stunden",
"wind_direction_type": "Windrichtungsangabentyp"
"entity_type": "Welcher Typ soll konfiguriert werden?"
}
},
"station_select": {
"description": "Some stations offer actual measured values (with a slight delay), which could provide more accurate data. However, the forecasts are still very accurate.\n\nThe station name is configured in the next step.",
"title": "Station selection",
"data": {
"station_id": "Station"
}
},
"station_configure_report": {
"description": "You have selected a station where actual measurement data is available.\nThe measured values might provide values with higher accuracy, but they are available only with a slight time delay (under 1 hour).",
"title": "Data source configuration",
"data": {
"data_type": "Data source"
}
},
"station_configure": {
"description": "DWD provides an hourly data update, however, this will increase data usage significantly as the sensor values are bundled in the hourly update (~37MB vs. ~0.17MB per update).",
"title": "Station configuration",
"data": {
"station_name": "Station name",
"wind_direction_type": "Wind direction type",
"hourly_update": "Force hourly data update (see warning above before enabling)"
}
},
"granularity": {
"description": "It is possible to average the forecast values for the weather entity over a given period of time. Please select the ones you want to use, the data is only fetched once.",
"title": "Forecast granularity",
"data": {
"weather_interval": "Forecast granularity"
}
}
},
"error": {
"cannot_connect": "Keine Verbindung möglich",
"invalid_station_id": "Stations-ID nicht korrekt",
"weather_interval_too_big": "Das maximale Intervall sind 24 Stunden",
"weather_interval_remainder_not_zero": "Der Rest von dem Wert geteilt durch 24 muss 0 ergeben",
"unknown": "Unbekannter Fehler"
"cannot_connect": "Cannot connect",
"invalid_station_id": "Invalid Station! Please select one from the list below:",
"invalid_weather_interval": "You have to select at least one weather interval",
"unknown": "Unknown Error"
},
"abort": {
"already_configured": "Diese Station wurde bereits konfiguriert"
"already_configured": "This station is already configured"
}
},
"selector": {
"entity_type": {
"options": {
"weather_station": "Station",
"weather_map": "Karte"
}
},
"data_type": {
"options": {
"report_data": "Use report data for current weather (slight delay in data possible)",
"forecast_data": "Use forecast data for current weather"
}
},
"wind_direction_type": {
"options": {
"degrees": "Degrees",
"direction": "Direction (N, NE, E, ...)"
}
},
"weather_interval": {
"options": {
"24": "daily",
"12": "half day",
"6": "6 hours",
"3": "3 hours",
"2": "2 hours",
"1": "1 hour"
}
}
}
}
2 changes: 1 addition & 1 deletion custom_components/dwd_weather/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"config": {
"step": {
"user": {
"description": "You can configure two types of entities: A weather station and a weather map. You can add as many as you like, the only restriction is, that each station id must be unique.",
"description": "Right now, only weather stations are supported, but more will come. You can add as many as you like, the only restriction is, that each station id must be unique.",
"title": "Deutscher Wetterdienst",
"data": {
"entity_type": "Which Entity Type would you like to configure?"
Expand Down
37 changes: 24 additions & 13 deletions custom_components/dwd_weather/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
from custom_components.dwd_weather.connector import DWDWeatherData
from custom_components.dwd_weather.entity import DWDWeatherEntity

from homeassistant.components.weather import WeatherEntity
from homeassistant.components.weather import (
WeatherEntity,
WeatherEntityFeature,
Forecast,
)
from homeassistant.const import (
TEMP_CELSIUS,
PRESSURE_HPA,
Expand All @@ -18,7 +22,6 @@
ATTRIBUTION,
CONF_STATION_ID,
CONF_STATION_NAME,
CONF_WEATHER_INTERVAL,
DOMAIN,
DWDWEATHER_DATA,
)
Expand All @@ -32,31 +35,44 @@ async def async_setup_entry(
"""Add a weather entity from a config_entry."""
hass_data = hass.data[DOMAIN][entry.entry_id]
if CONF_STATION_ID in entry.data:
for interval in entry.data[CONF_WEATHER_INTERVAL]:
async_add_entities([DWDWeather(entry.data, hass_data, interval)], False)
async_add_entities([DWDWeather(entry.data, hass_data)], False)


class DWDWeather(DWDWeatherEntity, WeatherEntity):
"""Implementation of DWD weather."""

def __init__(self, entry_data, hass_data, weather_interval):
def __init__(self, entry_data, hass_data):
"""Initialise the platform with a data instance and site."""

dwd_data: DWDWeatherData = hass_data[DWDWEATHER_DATA]
self._weather_interval = int(weather_interval)

name = f"{dwd_data._config[CONF_STATION_NAME]}{'_' + str(weather_interval) + 'h' if weather_interval != '24' else ''}"
unique_id = f"{dwd_data._config[CONF_STATION_ID]}_weather{'_' + str(weather_interval) if weather_interval != '24' else ''}"
name = f"{dwd_data._config[CONF_STATION_NAME]}"
unique_id = f"{dwd_data._config[CONF_STATION_ID]}_weather"
_LOGGER.debug(
"Setting up weather with id {} and name {}".format(unique_id, name)
)
super().__init__(hass_data, unique_id, name)

async def async_forecast_daily(self) -> list[Forecast] | None:
"""Return the daily forecast in native units."""
return self._connector.get_forecast(WeatherEntityFeature.FORECAST_DAILY)

async def async_forecast_hourly(self) -> list[Forecast] | None:
"""Return the hourly forecast in native units."""
return self._connector.get_forecast(WeatherEntityFeature.FORECAST_HOURLY)

@property
def condition(self):
"""Return the current condition."""
return self._connector.get_condition()

@property
def supported_features(self):
"""Return the current condition."""
return (
WeatherEntityFeature.FORECAST_HOURLY | WeatherEntityFeature.FORECAST_DAILY
)

@property
def native_temperature(self):
"""Return the temperature."""
Expand Down Expand Up @@ -117,11 +133,6 @@ def attribution(self):
"""Return the attribution."""
return ATTRIBUTION

@property
def forecast(self):
"""Return the forecast array."""
return self._connector.get_forecast(self._weather_interval)

@property
def extra_state_attributes(self):
"""Return data validity infos."""
Expand Down
Loading

0 comments on commit 9bddb1d

Please sign in to comment.