Skip to content
This repository was archived by the owner on Nov 19, 2021. It is now read-only.

Commit 7165daf

Browse files
committed
Initial commit
0 parents  commit 7165daf

File tree

9 files changed

+321
-0
lines changed

9 files changed

+321
-0
lines changed

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Coronavirus Hessen
2+
3+
[Home Assistant](https://www.home-assistant.io/) component to scrape the current SARS-CoV-2 data for the German state of Hessen from the [website of the Hessisches Ministerium für Soziales und Integration](https://soziales.hessen.de/gesundheit/infektionsschutz/coronavirus-sars-cov-2/taegliche-uebersicht-der-bestaetigten-sars-cov-2-faelle-hessen).
4+
5+
## Setup
6+
7+
<!--
8+
There are two ways to set this up:
9+
10+
#### 1. Using HACS
11+
12+
Open your HACS Settings and add
13+
14+
https://github.com/foosel/homeassistant-coronavirus-hessen
15+
16+
as custom repository URL.
17+
18+
Then install the "Coronavirus Hessen" integration.
19+
20+
If you use this method, your component will always update to the latest version.
21+
22+
#### 2. Manual
23+
-->
24+
25+
Copy the folder `custom_components/coronavirus_hessen` to `<ha_config_dir>/custom_components/`. When you are done you should have `<ha_config_dir>/custom_components/coronavirus_hessen/__init__.py`, `<ha_config_dir>/custom_components/coronavirus_hessen/sensor.py` and so on.
26+
27+
<!-- If you use this method then you'll need to keep an eye on this repository to check for updates. -->
28+
29+
## Configuration:
30+
31+
In Home Assistant:
32+
33+
1. Enter configuration menu
34+
2. Select "Integrations"
35+
3. Click the "+" in the bottom right
36+
4. Choose "Coronavirus Hessen"
37+
5. Choose the county you wish to monitor (or "Gesamthessen" for all of Hessen)
38+
6. Save
39+
40+
## TODO
41+
42+
* [ ] Find out why the created sensors don't show up in the integration overview
43+
* [ ] Find out if there's a possibility to select more than one county during configuration to have all created sensors under *one* integration entry
44+
* [ ] Make this thing work with HACS for easier installation/updating
45+
46+
*This is my first integration for Home Assistant ever and I basically learned how to even begin to do this stuff while writing this. I'm happy for any pointers as to how to improve things.*
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"config": {
3+
"title": "Coronavirus Hessen",
4+
"step": {
5+
"user": {
6+
"title": "Wähle einen Landkreis",
7+
"data": {
8+
"country": "Landkreis"
9+
}
10+
}
11+
},
12+
"abort": {
13+
"already_configured": "Dieser Landkreis ist bereits konfiguriert."
14+
}
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"config": {
3+
"title": "Coronavirus Hessen",
4+
"step": {
5+
"user": {
6+
"title": "Pick a county to monitor",
7+
"data": {
8+
"country": "County"
9+
}
10+
}
11+
},
12+
"abort": {
13+
"already_configured": "This county is already configured."
14+
}
15+
}
16+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""The corona_hessen component."""
2+
3+
from datetime import timedelta
4+
import logging
5+
6+
import async_timeout
7+
import asyncio
8+
import bs4
9+
10+
from homeassistant.config_entries import ConfigEntry
11+
from homeassistant.core import HomeAssistant, callback
12+
13+
from homeassistant.helpers import aiohttp_client
14+
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
15+
16+
from .const import DOMAIN, ENDPOINT, OPTION_TOTAL
17+
18+
_LOGGER = logging.getLogger(__name__)
19+
20+
PLATFORMS = ["sensor"]
21+
22+
async def async_setup(hass: HomeAssistant, config: dict):
23+
"""Set up the Coronavirus Hessen component."""
24+
# Make sure coordinator is initialized.
25+
await get_coordinator(hass)
26+
return True
27+
28+
29+
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
30+
"""Set up Coronavirus Hessen from a config entry."""
31+
32+
for component in PLATFORMS:
33+
hass.async_create_task(
34+
hass.config_entries.async_forward_entry_setup(entry, component)
35+
)
36+
37+
return True
38+
39+
40+
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
41+
"""Unload a config entry."""
42+
unload_ok = all(
43+
await asyncio.gather(
44+
*[
45+
hass.config_entries.async_forward_entry_unload(entry, component)
46+
for component in PLATFORMS
47+
]
48+
)
49+
)
50+
51+
return unload_ok
52+
53+
54+
async def get_coordinator(hass):
55+
"""Get the data update coordinator."""
56+
if DOMAIN in hass.data:
57+
return hass.data[DOMAIN]
58+
59+
async def async_get_data():
60+
with async_timeout.timeout(10):
61+
response = await aiohttp_client.async_get_clientsession(hass).get(ENDPOINT)
62+
raw_html = await response.text()
63+
64+
data = bs4.BeautifulSoup(raw_html, "html.parser")
65+
66+
result = dict()
67+
rows = data.select("article table:first-of-type tr")
68+
69+
# Counties
70+
for row in rows[1:-1]:
71+
line = row.select("td")
72+
if len(line) != 4:
73+
continue
74+
75+
try:
76+
county = line[0].text.strip()
77+
cases_str = line[3].text.strip()
78+
if len(cases_str) and cases_str != "-":
79+
cases = int(cases_str)
80+
else:
81+
cases = 0
82+
except ValueError:
83+
_LOGGER.error("Error processing line {}, skipping".format(line))
84+
continue
85+
result[county] = cases
86+
87+
# Total
88+
line = rows[-1].select("td")
89+
try:
90+
result[OPTION_TOTAL] = int(line[-1].select("p strong")[0].text.strip())
91+
except ValueError:
92+
_LOGGER.error("Error processing total value from {}, skipping".format(line))
93+
94+
_LOGGER.debug("Corona Hessen: {!r}".format(result))
95+
return result
96+
97+
hass.data[DOMAIN] = DataUpdateCoordinator(
98+
hass,
99+
logging.getLogger(__name__),
100+
name=DOMAIN,
101+
update_method=async_get_data,
102+
update_interval=timedelta(hours=12), # 12h as the data apparently only updates once per day anyhow
103+
)
104+
await hass.data[DOMAIN].async_refresh()
105+
return hass.data[DOMAIN]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""Config flow for Coronavirus Hessen integration."""
2+
import logging
3+
4+
import voluptuous as vol
5+
6+
from homeassistant import config_entries
7+
8+
from . import get_coordinator
9+
from .const import DOMAIN, OPTION_TOTAL
10+
11+
_LOGGER = logging.getLogger(__name__)
12+
13+
14+
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
15+
"""Handle a config flow for Coronavirus Hessen."""
16+
17+
VERSION = 1
18+
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
19+
20+
_options = None
21+
22+
async def async_step_user(self, user_input=None):
23+
"""Handle the initial step."""
24+
if self._options is None:
25+
self._options = {OPTION_TOTAL: "Gesamthessen"}
26+
coordinator = await get_coordinator(self.hass)
27+
for county in sorted(coordinator.data.keys()):
28+
if county == OPTION_TOTAL:
29+
continue
30+
self._options[county] = county
31+
32+
if user_input is not None:
33+
await self.async_set_unique_id(user_input["county"])
34+
self._abort_if_unique_id_configured()
35+
return self.async_create_entry(
36+
title=self._options[user_input["county"]], data=user_input
37+
)
38+
39+
_LOGGER.debug("Showing config form, options is {!r}".format(self._options))
40+
return self.async_show_form(
41+
step_id="user",
42+
data_schema=vol.Schema({
43+
vol.Required("county"): vol.In(self._options)
44+
}),
45+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Constants for the Coronavirus Hessen integration."""
2+
DOMAIN = "coronavirus_hessen"
3+
ENDPOINT = "https://soziales.hessen.de/gesundheit/infektionsschutz/coronavirus-sars-cov-2/taegliche-uebersicht-der-bestaetigten-sars-cov-2-faelle-hessen"
4+
ATTRIBUTION = "Data provided by Hessisches Ministrium für Soziales und Integration"
5+
OPTION_TOTAL = "total"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"domain": "coronavirus_hessen",
3+
"name": "Coronavirus Hessen",
4+
"config_flow": true,
5+
"documentation": "https://github.com/foosel/homeassistant-coronavirus-hessen",
6+
"requirements": ["beautifulsoup4==4.8.2"],
7+
"dependencies": [],
8+
"codeowners": ["@foosel"]
9+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Support for getting current Corona data from the website of the Hessische Ministerium für Soziales und Integration."""
2+
import logging
3+
4+
import voluptuous as vol
5+
6+
from homeassistant.const import ATTR_ATTRIBUTION
7+
from homeassistant.helpers.entity import Entity
8+
9+
from . import get_coordinator
10+
from .const import ATTRIBUTION, OPTION_TOTAL
11+
12+
_LOGGER = logging.getLogger(__name__)
13+
14+
async def async_setup_entry(hass, config_entry, async_add_entities):
15+
"""Defer sensor setup to the shared sensor module."""
16+
coordinator = await get_coordinator(hass)
17+
18+
async_add_entities([CoronaHessenSensor(coordinator, config_entry.data["county"])])
19+
20+
class CoronaHessenSensor(Entity):
21+
"""Representation of a county with Corona cases."""
22+
23+
def __init__(self, coordinator, county):
24+
"""Initialize sensor."""
25+
self.coordinator = coordinator
26+
self.county = county
27+
if county == OPTION_TOTAL:
28+
self._name = f"Coronavirus Hessen"
29+
else:
30+
self._name = f"Coronavirus Hessen {county}"
31+
self._state = None
32+
33+
@property
34+
def available(self):
35+
return self.coordinator.last_update_success and self.county in self.coordinator.data
36+
37+
@property
38+
def name(self):
39+
return self._name
40+
41+
@property
42+
def icon(self):
43+
return "mdi:biohazard"
44+
45+
@property
46+
def unit_of_measurement(self):
47+
return "people"
48+
49+
@property
50+
def state(self):
51+
return self.coordinator.data[self.county]
52+
53+
@property
54+
def device_state_attributes(self):
55+
return {ATTR_ATTRIBUTION: ATTRIBUTION}
56+
57+
async def async_added_to_hass(self):
58+
"""When entity is added to hass."""
59+
self.coordinator.async_add_listener(self.async_write_ha_state)
60+
61+
async def async_will_remove_from_hass(self):
62+
"""When entity will be removed from hass."""
63+
self.coordinator.async_remove_listener(self.async_write_ha_state)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"config": {
3+
"title": "Coronavirus Hessen",
4+
"step": {
5+
"user": {
6+
"title": "Pick a county to monitor",
7+
"data": {
8+
"country": "County"
9+
}
10+
}
11+
},
12+
"abort": {
13+
"already_configured": "This county is already configured."
14+
}
15+
}
16+
}

0 commit comments

Comments
 (0)