Skip to content

Commit

Permalink
Provide all forecasts in one entity via the new mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
hg1337 committed Aug 5, 2023
1 parent df7e2f2 commit 2760b66
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 28 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ To get there in one click, use this button:

[![Open your Home Assistant instance and start setting up this integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=dwd)

This adds one device and two entities (one with hourly forecast and one with daily forceast) for the selected station. To add more stations, just repeat the "Add Integration" step.
This adds one device and three entities for the selected station. By default, only the entity that provides all forecasts in one entity is enabled. However, if you still need the weather entities with daily and houry forecasts separately or via the old mechanism, you can just enable them. To add more stations, just repeat the "Add Integration" step.

## Questions & Answers

Expand All @@ -97,13 +97,13 @@ For issues with measurement data (current condition, current temperature, ...),
- When the problem occurs, download immediately `https://opendata.dwd.de/weather/weather_reports/poi/{station_id}-BEOB.csv`. Replace `{station_id}` with your actual station ID. If your station ID has less than 5 characters, it has to be padded with trailing underscores, so e.g. "A191" becomes "A191_" here.

### Issues with Hourly Forecasts
For issues with hourly forecasts (i.e. the forecasts of the entity ending with "\_hourly"), please include the following items in your bug report.
For issues with hourly forecasts, please include the following items in your bug report.
- Always include the station ID.
- When the problem occurs, go to the Developer Tools and copy immediately all state attributes (YAML) of the hourly entity (the entity ending with "\_hourly").
- When the problem occurs, download immediately `https://opendata.dwd.de/weather/local_forecasts/mos/MOSMIX_L/single_stations/{station_id}/kml/MOSMIX_L_LATEST_{station_id}.kmz`. Replace `{station_id}` with your actual station ID. No padding needed here.

### Issues with Daily Forecasts
For issues with daily forecasts (i.e. the forecasts of the entity ending with "\_daily"), please include the following items in your bug report.
For issues with daily forecasts, please include the following items in your bug report.
- Always include the station ID.
- When the problem occurs, go to the Developer Tools and copy immediately all state attributes (YAML) of the hourly entity (the entity ending with "\_hourly"). This is important, because the daily forecasts are calculated from hourly forecasts.
- When the problem occurs, go to the Developer Tools and copy immediately all state attributes (YAML) of the daily entity (the entity ending with "\_daily").
Expand Down
3 changes: 0 additions & 3 deletions custom_components/dwd/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@

MEASUREMENTS_MAX_AGE = 3

FORECAST_MODE_DAILY = 0
FORECAST_MODE_HOURLY = 1

DWD_MEASUREMENT = 0
DWD_FORECAST = 1

Expand Down
2 changes: 1 addition & 1 deletion custom_components/dwd/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/hg1337/homeassistant-dwd/issues",
"requirements": [],
"version": "2023.8.0"
"version": "2023.8.1"
}
67 changes: 47 additions & 20 deletions custom_components/dwd/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

from datetime import date, datetime, time, timedelta, timezone
from enum import Enum
import logging
from typing import Any, Optional
from homeassistant.components.weather import WeatherEntityFeature
Expand Down Expand Up @@ -73,13 +74,17 @@
DWD_MEASUREMENT_PRESSURE,
DWD_MEASUREMENT_TEMPERATURE,
DWD_MEASUREMENT_VISIBILITY,
FORECAST_MODE_DAILY,
FORECAST_MODE_HOURLY,
)

_LOGGER = logging.getLogger(__name__)


class ForecastMode(Enum):
STANDARD = 0
DAILY = 1
HOURLY = 2


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
Expand All @@ -98,20 +103,28 @@ async def async_setup_entry(

async_add_entities(
[
DwdWeather(
hass,
coordinator,
config_entry.unique_id,
config_entry,
ForecastMode.STANDARD,
device,
),
DwdWeather(
hass,
coordinator,
f"{config_entry.unique_id}-daily",
config_entry,
FORECAST_MODE_DAILY,
ForecastMode.DAILY,
device,
),
DwdWeather(
hass,
coordinator,
f"{config_entry.unique_id}-hourly",
config_entry,
FORECAST_MODE_HOURLY,
ForecastMode.HOURLY,
device,
),
]
Expand All @@ -127,26 +140,32 @@ def __init__(
coordinator: DwdDataUpdateCoordinator,
unique_id: str,
config: ConfigEntry,
forecast_mode: int,
forecast_mode: ForecastMode,
device: DeviceInfo,
) -> None:
"""Initialise the platform with a data instance and site."""
super().__init__(coordinator)
self._hass: HomeAssistant = hass
self._unique_id: str = unique_id
self._config: ConfigEntry = config
self._forecast_mode: int = forecast_mode
self._forecast_mode: ForecastMode = forecast_mode
self._device: DeviceInfo = device
self._conf_current_weather: str = self._config.options.get(
CONF_CURRENT_WEATHER, CONF_CURRENT_WEATHER_DEFAULT
)

self._attr_supported_features = 0
if self._config.options.get(CONF_FORECAST, CONF_FORECAST_DEFAULT):
if self._forecast_mode == FORECAST_MODE_HOURLY:
self._attr_supported_features |= WeatherEntityFeature.FORECAST_HOURLY
if self._forecast_mode == FORECAST_MODE_DAILY:
if (
self._forecast_mode == ForecastMode.STANDARD
or self._forecast_mode == ForecastMode.DAILY
):
self._attr_supported_features |= WeatherEntityFeature.FORECAST_DAILY
if (
self._forecast_mode == ForecastMode.STANDARD
or self._forecast_mode == ForecastMode.HOURLY
):
self._attr_supported_features |= WeatherEntityFeature.FORECAST_HOURLY

@property
def unique_id(self) -> str:
Expand All @@ -160,9 +179,9 @@ def name(self) -> str:
name = self._config.title
name_appendix = ""

if self._forecast_mode == FORECAST_MODE_HOURLY:
if self._forecast_mode == ForecastMode.HOURLY:
name_appendix = " Hourly"
if self._forecast_mode == FORECAST_MODE_DAILY:
if self._forecast_mode == ForecastMode.DAILY:
name_appendix = " Daily"

if name is None:
Expand All @@ -183,6 +202,11 @@ def device_info(self) -> DeviceInfo:
"""Device info."""
return self._device

@property
def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry."""
return self._forecast_mode == ForecastMode.STANDARD

@property
def condition(self) -> str | None:
"""Return the current condition."""
Expand All @@ -197,7 +221,7 @@ def condition(self) -> str | None:
if self._conf_current_weather == CONF_CURRENT_WEATHER_MEASUREMENT:
return None
else:
forecast = self._get_forecast(FORECAST_MODE_HOURLY, 1)
forecast = self._get_forecast(ForecastMode.HOURLY, 1)
if forecast is None or len(forecast) < 1:
return None
else:
Expand All @@ -208,7 +232,7 @@ def condition(self) -> str | None:
condition = ATTR_CONDITION_CLEAR_NIGHT
return condition
elif self._conf_current_weather == CONF_CURRENT_WEATHER_FORECAST:
forecast = self._get_forecast(FORECAST_MODE_HOURLY, 1)
forecast = self._get_forecast(ForecastMode.HOURLY, 1)
if forecast is None or len(forecast) < 1:
return None
else:
Expand Down Expand Up @@ -289,15 +313,15 @@ def _get_float_measurement_with_fallback(
if self._conf_current_weather == CONF_CURRENT_WEATHER_MEASUREMENT:
return None
else:
forecast = self._get_forecast(FORECAST_MODE_HOURLY, 1)
forecast = self._get_forecast(ForecastMode.HOURLY, 1)
if forecast is None or len(forecast) < 1:
return None
else:
return forecast[0].get(attr_forecast)
else:
return DwdWeather._str_to_float(str_value)
elif self._conf_current_weather == CONF_CURRENT_WEATHER_FORECAST:
forecast = self._get_forecast(FORECAST_MODE_HOURLY, 1)
forecast = self._get_forecast(ForecastMode.HOURLY, 1)
if forecast is None or len(forecast) < 1:
return None
else:
Expand Down Expand Up @@ -334,6 +358,9 @@ def forecast(self):
if not self._config.options.get(CONF_FORECAST, CONF_FORECAST_DEFAULT):
return None

if self._forecast_mode == ForecastMode.STANDARD:
return None

return self._get_forecast(self._forecast_mode)

async def async_forecast_daily(self):
Expand All @@ -342,17 +369,17 @@ async def async_forecast_daily(self):
if not self._config.options.get(CONF_FORECAST, CONF_FORECAST_DEFAULT):
return None

return self._get_forecast(FORECAST_MODE_DAILY)
return self._get_forecast(ForecastMode.DAILY)

async def async_forecast_hourly(self):
"""Return the daily forecast."""

if not self._config.options.get(CONF_FORECAST, CONF_FORECAST_DEFAULT):
return None

return self._get_forecast(FORECAST_MODE_HOURLY)
return self._get_forecast(ForecastMode.HOURLY)

def _get_forecast(self, forecast_mode: int, max_hours: int = 0):
def _get_forecast(self, forecast_mode: ForecastMode, max_hours: int = 0):
# We build both lists in parallel and just return the needed one. Although it's a small
# overhead, it still makes thinks easier, because there is still much in common, because to
# calculate the days most of the hourly stuff has to be done again.
Expand Down Expand Up @@ -756,7 +783,7 @@ def _get_forecast(self, forecast_mode: int, max_hours: int = 0):
if max_hours > 0 and len(hourly_list) >= max_hours:
break

if forecast_mode == FORECAST_MODE_DAILY:
if forecast_mode == ForecastMode.DAILY:
result = []
if len(daily_list) > 0:
# Always add current day:
Expand All @@ -766,7 +793,7 @@ def _get_forecast(self, forecast_mode: int, max_hours: int = 0):
if daily_list[i].has_enough_hours:
result.append(daily_list[i].values)
return result
if forecast_mode == FORECAST_MODE_HOURLY:
if forecast_mode == ForecastMode.HOURLY:
return hourly_list

@staticmethod
Expand Down
Binary file modified images/screenshot_entities.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/screenshot_weather-forecast-card-configuration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ If you don't want to use the My Home Assistant button or if it doesn't work in y

Follow the instructions, select a different station or enter a custom one if needed. By default, the closest station that provides measurement as well as forcast data is preselected, if it is not more than 20 km away and if the difference in elevation is less than 500 m. Otherwise the closest available station is preselected.

After that, you should have one new device and two new weather entities for the selected station, one entity with hourly forecast and one entity with daily forceast. Both have the same measurement data. You may repeat these steps if you want to add more stations.
After that, you should have one new device and three new weather entities for the selected station. By default, only the entity that provides all forecasts in one entity is enabled. However, if you still need the weather entities with daily and houry forecasts separately or via the old mechanism, you can just enable them. All have the same measurement data. You may repeat these steps if you want to add more stations.

![Screenshot Entities](./images/screenshot_entities.png)

Expand Down

0 comments on commit 2760b66

Please sign in to comment.