Skip to content

Commit 2aadeb3

Browse files
committed
add readme.md and minor refactorings
1 parent f94d5b6 commit 2aadeb3

File tree

6 files changed

+109
-40
lines changed

6 files changed

+109
-40
lines changed

README.md

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# DWD Pollenflug
2+
3+
This component adds pollen forecasts from [Deutscher Wetterdienst (DWD)](https://www.dwd.de/DE/leistungen/gefahrenindizespollen/gefahrenindexpollen.html) to Home Assistant.
4+
5+
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).
6+
7+
A forecast is provided for the following grass and tree pollen:
8+
- Alder (Erle)
9+
- Ambrosia (Ambrosia)
10+
- Ash (Esche)
11+
- Birch (Birke)
12+
- Hazel (Hasel)
13+
- Grass (Gräser)
14+
- Mugwort (Beifuss)
15+
- Rye (Roggen)
16+
17+
This component fetches data every hour from DWD (although the data is only updated only once per day).
18+
19+
If you like this component, please give it a star on [github](https://github.com/mampfes/hacs_dwd_pollenflug).
20+
21+
## Installation
22+
23+
1. Ensure that [HACS](https://hacs.xyz) is installed.
24+
2. Install **DWD Pollenflug** integration via HACS.
25+
3. Add **DWD Pollenflug** integration to Home Assistant (one per region):
26+
27+
[![](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start?domain=dwd_pollenflug)
28+
29+
In case you would like to install manually:
30+
31+
1. Copy the folder `custom_components/dwd_pollenflug` to `custom_components` in your Home Assistant `config` folder.
32+
2. Add **DWD Pollenflug** integration to Home Assistant (one per region):
33+
34+
[![](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start?domain=dwd_pollenflug)
35+
36+
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.
37+
38+
## Sensors
39+
40+
This integration provides one sensor per pollen type. The sensor is named according to the pollen type.
41+
42+
### Sensor State
43+
44+
The sensor state represents an index of the pollen count. DWD is using the following index range:
45+
46+
Index | Description (in German)
47+
------|-----------------------------------
48+
0 | keine Belastung
49+
0.5 | keine bis geringe Belastung
50+
1 | geringe Belastung
51+
1.5 | geringe bis mittlere Belastung
52+
2 | mittlere Belastung
53+
2.5 | mittlere bis hohe Belastung
54+
3 | hohe Belastung
55+
56+
57+
### Sensor Attributes
58+
59+
Each sensor provides the following attributes (not including default attributes):
60+
61+
Attribute | Example | Description
62+
--------------------|---------------------------|-------------------------------------------------------------------------
63+
state_tomorrow | 1 | Forecast for tomorrow.
64+
state_in_2_days | 1 | Forecast for the day after tomorrow.
65+
state_today_desc | geringe Belastung | Human readable description of the forecast for today [in German].
66+
state_tomorrow_desc | geringe Belastung | Human readable description of the forecast for tomorrow [in German].
67+
state_in_2_days_desc| geringe Belastung | Human readable description of the forecast for the day after tomorrow [in German].
68+
last_update | 2021-09-28T11:00:00+02:00 | Timestamp representing the last update of the data by DWD.
69+
next_update | 2021-09-29T11:00:00+02:00 | Timestamp representing the next update of the data by DWD.

custom_components/dwd_pollenflug/DWD/Pollenflug/__init__.py

+9-6
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
import re
33
from datetime import datetime, timedelta
44

5+
import pytz
56
import requests
67

7-
LOGGER = logging.getLogger(__name__)
8-
LOGGER.setLevel(logging.DEBUG)
8+
_LOGGER = logging.getLogger(__name__)
99

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

7374
def __init__(self):
7475
self._last_update = None
@@ -163,11 +164,13 @@ def _extract_regions_with_data(self, data):
163164
return regions
164165

165166
def _extract_data(self, data):
166-
self._last_update = datetime.strptime(
167-
data["last_update"], self._TIME_FORMAT_STR
167+
# define timezone for DWD data
168+
tz = pytz.timezone(self._TIME_ZONE)
169+
self._last_update = tz.localize(
170+
datetime.strptime(data["last_update"], self._TIME_FORMAT_STR)
168171
)
169-
self._next_update = datetime.strptime(
170-
data["next_update"], self._TIME_FORMAT_STR
172+
self._next_update = tz.localize(
173+
datetime.strptime(data["next_update"], self._TIME_FORMAT_STR)
171174
)
172175
self._name = data["name"]
173176
self._sender = data["sender"]

custom_components/dwd_pollenflug/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
2727
try:
2828
await hass.async_add_executor_job(shell._fetch)
2929
except Exception as exc:
30-
_LOGGER.error("Fetch data from DWD failed")
30+
_LOGGER.error("fetch data from DWD failed")
3131
raise ConfigEntryNotReady from exc
3232

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

7676
self._regions[config_entry.data[CONF_REGION_ID]] = config_entry
@@ -96,4 +96,4 @@ def _fetch(self, *_):
9696
try:
9797
self._source.fetch()
9898
except Exception as error:
99-
_LOGGER.error(f"fetch failed : {error}")
99+
_LOGGER.error(f"fetch data from DWD failed : {error}")

custom_components/dwd_pollenflug/manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
"documentation": "https://github.com/mampfes/hacs_dwd_pollenflug",
77
"codeowners": ["@mampfes"],
88
"iot_class": "cloud_polling",
9-
"version": "0.0.0"
9+
"version": "1.0.0"
1010
}

custom_components/dwd_pollenflug/sensor.py

+25-29
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,22 @@
22
from datetime import timedelta
33

44
from homeassistant.components.sensor import SensorEntity
5-
from homeassistant.const import ATTR_ATTRIBUTION
5+
from homeassistant.const import (
6+
ATTR_ATTRIBUTION,
7+
ATTR_IDENTIFIERS,
8+
ATTR_MANUFACTURER,
9+
ATTR_MODEL,
10+
ATTR_NAME,
11+
)
612
from homeassistant.util.dt import utcnow
713

814
from .const import CONF_REGION_ID, DOMAIN
915

1016
ATTR_STATE_TOMORROW = "state_tomorrow"
1117
ATTR_STATE_IN_2_DAYS = "state_in_2_days"
12-
ATTR_DESC_TODAY = "value_today"
13-
ATTR_DESC_TOMORROW = "value_tomorrow"
14-
ATTR_DESC_IN_2_DAYS = "value_in_2_days"
18+
ATTR_DESC_TODAY = "state_today_desc"
19+
ATTR_DESC_TOMORROW = "state_tomorrow_desc"
20+
ATTR_DESC_IN_2_DAYS = "state_in_2_days_desc"
1521
ATTR_LAST_UPDATE = "last_update"
1622
ATTR_NEXT_UPDATE = "next_update"
1723

@@ -46,18 +52,20 @@ class PollenflugSensorEntity(SensorEntity):
4652
def __init__(self, hass, source, region_id, pollen_name):
4753
self._source = source
4854
self._region_id = region_id
49-
self._name = f"Pollenflug {pollen_name}"
55+
self._pollen_name = pollen_name
5056
self._attributes = {}
51-
52-
self._unique_id = f"{DOMAIN}_{pollen_name}_{region_id}"
5357
self._state = None
5458
self._update_sensor_listener = None
5559

56-
self._device_info = {
57-
"identifiers": {(DOMAIN, region_id)},
58-
"name": "Pollenflug-Gefahrenindex",
59-
"manufacturer": source.sender,
60-
"model": source.regions_list[region_id].name,
60+
# set HA instance attributes directly (don't use property)
61+
self._attr_unique_id = f"{DOMAIN}_{pollen_name}_{region_id}"
62+
self._attr_name = f"Pollenflug {pollen_name} {region_id}"
63+
self._attr_icon = "mdi:flower-pollen"
64+
self._attr_device_info = {
65+
ATTR_IDENTIFIERS: {(DOMAIN, region_id)},
66+
ATTR_NAME: "Pollenflug-Gefahrenindex",
67+
ATTR_MANUFACTURER: source.sender,
68+
ATTR_MODEL: source.regions_list[region_id].name,
6169
"entry_type": "service",
6270
}
6371

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

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

93-
self._attributes[ATTR_ATTRIBUTION] = f"Last update: {self._source.last_update}"
94-
95-
@property
96-
def device_info(self):
97-
"""Return device info which is shared between all entities of a device."""
98-
return self._device_info
101+
# return last update in local timezone
102+
self._attributes[
103+
ATTR_ATTRIBUTION
104+
] = f"Last update: {self._source.last_update.astimezone()}"
99105

100106
@property
101107
def device_state_attributes(self):
102108
"""Return attributes for the entity."""
103109
return self._attributes
104110

105-
@property
106-
def unique_id(self):
107-
"""Return unique id for entity."""
108-
return self._unique_id
109-
110-
@property
111-
def name(self):
112-
"""Return entity name."""
113-
return self._name
114-
115111
@property
116112
def available(self):
117113
"""Return true if value is valid."""

hacs.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
"name": "DWD Pollenflug",
33
"domains": ["sensor"],
44
"iot_class": "cloud_polling",
5-
"render_readme": true
5+
"render_readme": true,
6+
"requirements": ["pytz"]
67
}

0 commit comments

Comments
 (0)