Skip to content

Commit

Permalink
add readme.md and minor refactorings
Browse files Browse the repository at this point in the history
  • Loading branch information
mampfes committed Sep 29, 2021
1 parent f94d5b6 commit 2aadeb3
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 40 deletions.
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# DWD Pollenflug

This component adds pollen forecasts from [Deutscher Wetterdienst (DWD)](https://www.dwd.de/DE/leistungen/gefahrenindizespollen/gefahrenindexpollen.html) to Home Assistant.

The DWD provides forecasts for 27 regions in Germany. The data will be updated daily (currently at 11am) and the forecasts include the data for today and tomorrow (and the day after tomorrow on Friday).

A forecast is provided for the following grass and tree pollen:
- Alder (Erle)
- Ambrosia (Ambrosia)
- Ash (Esche)
- Birch (Birke)
- Hazel (Hasel)
- Grass (Gräser)
- Mugwort (Beifuss)
- Rye (Roggen)

This component fetches data every hour from DWD (although the data is only updated only once per day).

If you like this component, please give it a star on [github](https://github.com/mampfes/hacs_dwd_pollenflug).

## Installation

1. Ensure that [HACS](https://hacs.xyz) is installed.
2. Install **DWD Pollenflug** integration via HACS.
3. Add **DWD Pollenflug** integration to Home Assistant (one per region):

[![](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start?domain=dwd_pollenflug)

In case you would like to install manually:

1. Copy the folder `custom_components/dwd_pollenflug` to `custom_components` in your Home Assistant `config` folder.
2. Add **DWD Pollenflug** integration to Home Assistant (one per region):

[![](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start?domain=dwd_pollenflug)

One instance of **DWD Pollenflug** covers one region. If you want to get the data from multiple regions, just add multiple instance of the **DWD Pollenflug** integration.

## Sensors

This integration provides one sensor per pollen type. The sensor is named according to the pollen type.

### Sensor State

The sensor state represents an index of the pollen count. DWD is using the following index range:

Index | Description (in German)
------|-----------------------------------
0 | keine Belastung
0.5 | keine bis geringe Belastung
1 | geringe Belastung
1.5 | geringe bis mittlere Belastung
2 | mittlere Belastung
2.5 | mittlere bis hohe Belastung
3 | hohe Belastung


### Sensor Attributes

Each sensor provides the following attributes (not including default attributes):

Attribute | Example | Description
--------------------|---------------------------|-------------------------------------------------------------------------
state_tomorrow | 1 | Forecast for tomorrow.
state_in_2_days | 1 | Forecast for the day after tomorrow.
state_today_desc | geringe Belastung | Human readable description of the forecast for today [in German].
state_tomorrow_desc | geringe Belastung | Human readable description of the forecast for tomorrow [in German].
state_in_2_days_desc| geringe Belastung | Human readable description of the forecast for the day after tomorrow [in German].
last_update | 2021-09-28T11:00:00+02:00 | Timestamp representing the last update of the data by DWD.
next_update | 2021-09-29T11:00:00+02:00 | Timestamp representing the next update of the data by DWD.
15 changes: 9 additions & 6 deletions custom_components/dwd_pollenflug/DWD/Pollenflug/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import re
from datetime import datetime, timedelta

import pytz
import requests

LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)
_LOGGER = logging.getLogger(__name__)

# maps keynames to day offsets
PREDICTION_LIST = {
Expand Down Expand Up @@ -69,6 +69,7 @@ class Pollenflug:
URL = "https://opendata.dwd.de/climate_environment/health/alerts/s31fg.json"
DESC = "https://opendata.dwd.de/climate_environment/health/alerts/Beschreibung_pollen_s31fg.pdf"
_TIME_FORMAT_STR = "%Y-%m-%d %H:%M Uhr"
_TIME_ZONE = "Europe/Berlin"

def __init__(self):
self._last_update = None
Expand Down Expand Up @@ -163,11 +164,13 @@ def _extract_regions_with_data(self, data):
return regions

def _extract_data(self, data):
self._last_update = datetime.strptime(
data["last_update"], self._TIME_FORMAT_STR
# define timezone for DWD data
tz = pytz.timezone(self._TIME_ZONE)
self._last_update = tz.localize(
datetime.strptime(data["last_update"], self._TIME_FORMAT_STR)
)
self._next_update = datetime.strptime(
data["next_update"], self._TIME_FORMAT_STR
self._next_update = tz.localize(
datetime.strptime(data["next_update"], self._TIME_FORMAT_STR)
)
self._name = data["name"]
self._sender = data["sender"]
Expand Down
6 changes: 3 additions & 3 deletions custom_components/dwd_pollenflug/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
try:
await hass.async_add_executor_job(shell._fetch)
except Exception as exc:
_LOGGER.error("Fetch data from DWD failed")
_LOGGER.error("fetch data from DWD failed")
raise ConfigEntryNotReady from exc

# add pollen region to shell
Expand Down Expand Up @@ -70,7 +70,7 @@ def add_entry(self, config_entry: ConfigEntry):
if self.is_idle():
# This is the first entry, therefore start the timer
self._fetch_callback_listener = async_track_time_interval(
self._hass, self._fetch_callback, timedelta(seconds=20)
self._hass, self._fetch_callback, timedelta(hours=1)
)

self._regions[config_entry.data[CONF_REGION_ID]] = config_entry
Expand All @@ -96,4 +96,4 @@ def _fetch(self, *_):
try:
self._source.fetch()
except Exception as error:
_LOGGER.error(f"fetch failed : {error}")
_LOGGER.error(f"fetch data from DWD failed : {error}")
2 changes: 1 addition & 1 deletion custom_components/dwd_pollenflug/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://github.com/mampfes/hacs_dwd_pollenflug",
"codeowners": ["@mampfes"],
"iot_class": "cloud_polling",
"version": "0.0.0"
"version": "1.0.0"
}
54 changes: 25 additions & 29 deletions custom_components/dwd_pollenflug/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@
from datetime import timedelta

from homeassistant.components.sensor import SensorEntity
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.const import (
ATTR_ATTRIBUTION,
ATTR_IDENTIFIERS,
ATTR_MANUFACTURER,
ATTR_MODEL,
ATTR_NAME,
)
from homeassistant.util.dt import utcnow

from .const import CONF_REGION_ID, DOMAIN

ATTR_STATE_TOMORROW = "state_tomorrow"
ATTR_STATE_IN_2_DAYS = "state_in_2_days"
ATTR_DESC_TODAY = "value_today"
ATTR_DESC_TOMORROW = "value_tomorrow"
ATTR_DESC_IN_2_DAYS = "value_in_2_days"
ATTR_DESC_TODAY = "state_today_desc"
ATTR_DESC_TOMORROW = "state_tomorrow_desc"
ATTR_DESC_IN_2_DAYS = "state_in_2_days_desc"
ATTR_LAST_UPDATE = "last_update"
ATTR_NEXT_UPDATE = "next_update"

Expand Down Expand Up @@ -46,18 +52,20 @@ class PollenflugSensorEntity(SensorEntity):
def __init__(self, hass, source, region_id, pollen_name):
self._source = source
self._region_id = region_id
self._name = f"Pollenflug {pollen_name}"
self._pollen_name = pollen_name
self._attributes = {}

self._unique_id = f"{DOMAIN}_{pollen_name}_{region_id}"
self._state = None
self._update_sensor_listener = None

self._device_info = {
"identifiers": {(DOMAIN, region_id)},
"name": "Pollenflug-Gefahrenindex",
"manufacturer": source.sender,
"model": source.regions_list[region_id].name,
# set HA instance attributes directly (don't use property)
self._attr_unique_id = f"{DOMAIN}_{pollen_name}_{region_id}"
self._attr_name = f"Pollenflug {pollen_name} {region_id}"
self._attr_icon = "mdi:flower-pollen"
self._attr_device_info = {
ATTR_IDENTIFIERS: {(DOMAIN, region_id)},
ATTR_NAME: "Pollenflug-Gefahrenindex",
ATTR_MANUFACTURER: source.sender,
ATTR_MODEL: source.regions_list[region_id].name,
"entry_type": "service",
}

Expand All @@ -71,7 +79,7 @@ async def async_update(self):
state_in_2_days = None

for pollen in self._source.pollen_list:
if pollen.region_id == self._region_id and pollen.name == self._name:
if pollen.region_id == self._region_id and pollen.name == self._pollen_name:
if pollen.date == today:
state_today = pollen.value
elif pollen.date == today + timedelta(days=1):
Expand All @@ -90,28 +98,16 @@ async def async_update(self):
self._attributes[ATTR_LAST_UPDATE] = self._source.last_update
self._attributes[ATTR_NEXT_UPDATE] = self._source.next_update

self._attributes[ATTR_ATTRIBUTION] = f"Last update: {self._source.last_update}"

@property
def device_info(self):
"""Return device info which is shared between all entities of a device."""
return self._device_info
# return last update in local timezone
self._attributes[
ATTR_ATTRIBUTION
] = f"Last update: {self._source.last_update.astimezone()}"

@property
def device_state_attributes(self):
"""Return attributes for the entity."""
return self._attributes

@property
def unique_id(self):
"""Return unique id for entity."""
return self._unique_id

@property
def name(self):
"""Return entity name."""
return self._name

@property
def available(self):
"""Return true if value is valid."""
Expand Down
3 changes: 2 additions & 1 deletion hacs.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"name": "DWD Pollenflug",
"domains": ["sensor"],
"iot_class": "cloud_polling",
"render_readme": true
"render_readme": true,
"requirements": ["pytz"]
}

0 comments on commit 2aadeb3

Please sign in to comment.