This repository has been archived by the owner on Nov 19, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 7165daf
Showing
9 changed files
with
321 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Coronavirus Hessen | ||
|
||
[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). | ||
|
||
## Setup | ||
|
||
<!-- | ||
There are two ways to set this up: | ||
#### 1. Using HACS | ||
Open your HACS Settings and add | ||
https://github.com/foosel/homeassistant-coronavirus-hessen | ||
as custom repository URL. | ||
Then install the "Coronavirus Hessen" integration. | ||
If you use this method, your component will always update to the latest version. | ||
#### 2. Manual | ||
--> | ||
|
||
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. | ||
|
||
<!-- If you use this method then you'll need to keep an eye on this repository to check for updates. --> | ||
|
||
## Configuration: | ||
|
||
In Home Assistant: | ||
|
||
1. Enter configuration menu | ||
2. Select "Integrations" | ||
3. Click the "+" in the bottom right | ||
4. Choose "Coronavirus Hessen" | ||
5. Choose the county you wish to monitor (or "Gesamthessen" for all of Hessen) | ||
6. Save | ||
|
||
## TODO | ||
|
||
* [ ] Find out why the created sensors don't show up in the integration overview | ||
* [ ] Find out if there's a possibility to select more than one county during configuration to have all created sensors under *one* integration entry | ||
* [ ] Make this thing work with HACS for easier installation/updating | ||
|
||
*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.* |
16 changes: 16 additions & 0 deletions
16
custom_components/coronavirus_hessen/.translations/de.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"config": { | ||
"title": "Coronavirus Hessen", | ||
"step": { | ||
"user": { | ||
"title": "Wähle einen Landkreis", | ||
"data": { | ||
"country": "Landkreis" | ||
} | ||
} | ||
}, | ||
"abort": { | ||
"already_configured": "Dieser Landkreis ist bereits konfiguriert." | ||
} | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
custom_components/coronavirus_hessen/.translations/en.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"config": { | ||
"title": "Coronavirus Hessen", | ||
"step": { | ||
"user": { | ||
"title": "Pick a county to monitor", | ||
"data": { | ||
"country": "County" | ||
} | ||
} | ||
}, | ||
"abort": { | ||
"already_configured": "This county is already configured." | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
"""The corona_hessen component.""" | ||
|
||
from datetime import timedelta | ||
import logging | ||
|
||
import async_timeout | ||
import asyncio | ||
import bs4 | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.core import HomeAssistant, callback | ||
|
||
from homeassistant.helpers import aiohttp_client | ||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed | ||
|
||
from .const import DOMAIN, ENDPOINT, OPTION_TOTAL | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
PLATFORMS = ["sensor"] | ||
|
||
async def async_setup(hass: HomeAssistant, config: dict): | ||
"""Set up the Coronavirus Hessen component.""" | ||
# Make sure coordinator is initialized. | ||
await get_coordinator(hass) | ||
return True | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): | ||
"""Set up Coronavirus Hessen from a config entry.""" | ||
|
||
for component in PLATFORMS: | ||
hass.async_create_task( | ||
hass.config_entries.async_forward_entry_setup(entry, component) | ||
) | ||
|
||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): | ||
"""Unload a config entry.""" | ||
unload_ok = all( | ||
await asyncio.gather( | ||
*[ | ||
hass.config_entries.async_forward_entry_unload(entry, component) | ||
for component in PLATFORMS | ||
] | ||
) | ||
) | ||
|
||
return unload_ok | ||
|
||
|
||
async def get_coordinator(hass): | ||
"""Get the data update coordinator.""" | ||
if DOMAIN in hass.data: | ||
return hass.data[DOMAIN] | ||
|
||
async def async_get_data(): | ||
with async_timeout.timeout(10): | ||
response = await aiohttp_client.async_get_clientsession(hass).get(ENDPOINT) | ||
raw_html = await response.text() | ||
|
||
data = bs4.BeautifulSoup(raw_html, "html.parser") | ||
|
||
result = dict() | ||
rows = data.select("article table:first-of-type tr") | ||
|
||
# Counties | ||
for row in rows[1:-1]: | ||
line = row.select("td") | ||
if len(line) != 4: | ||
continue | ||
|
||
try: | ||
county = line[0].text.strip() | ||
cases_str = line[3].text.strip() | ||
if len(cases_str) and cases_str != "-": | ||
cases = int(cases_str) | ||
else: | ||
cases = 0 | ||
except ValueError: | ||
_LOGGER.error("Error processing line {}, skipping".format(line)) | ||
continue | ||
result[county] = cases | ||
|
||
# Total | ||
line = rows[-1].select("td") | ||
try: | ||
result[OPTION_TOTAL] = int(line[-1].select("p strong")[0].text.strip()) | ||
except ValueError: | ||
_LOGGER.error("Error processing total value from {}, skipping".format(line)) | ||
|
||
_LOGGER.debug("Corona Hessen: {!r}".format(result)) | ||
return result | ||
|
||
hass.data[DOMAIN] = DataUpdateCoordinator( | ||
hass, | ||
logging.getLogger(__name__), | ||
name=DOMAIN, | ||
update_method=async_get_data, | ||
update_interval=timedelta(hours=12), # 12h as the data apparently only updates once per day anyhow | ||
) | ||
await hass.data[DOMAIN].async_refresh() | ||
return hass.data[DOMAIN] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
"""Config flow for Coronavirus Hessen integration.""" | ||
import logging | ||
|
||
import voluptuous as vol | ||
|
||
from homeassistant import config_entries | ||
|
||
from . import get_coordinator | ||
from .const import DOMAIN, OPTION_TOTAL | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): | ||
"""Handle a config flow for Coronavirus Hessen.""" | ||
|
||
VERSION = 1 | ||
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL | ||
|
||
_options = None | ||
|
||
async def async_step_user(self, user_input=None): | ||
"""Handle the initial step.""" | ||
if self._options is None: | ||
self._options = {OPTION_TOTAL: "Gesamthessen"} | ||
coordinator = await get_coordinator(self.hass) | ||
for county in sorted(coordinator.data.keys()): | ||
if county == OPTION_TOTAL: | ||
continue | ||
self._options[county] = county | ||
|
||
if user_input is not None: | ||
await self.async_set_unique_id(user_input["county"]) | ||
self._abort_if_unique_id_configured() | ||
return self.async_create_entry( | ||
title=self._options[user_input["county"]], data=user_input | ||
) | ||
|
||
_LOGGER.debug("Showing config form, options is {!r}".format(self._options)) | ||
return self.async_show_form( | ||
step_id="user", | ||
data_schema=vol.Schema({ | ||
vol.Required("county"): vol.In(self._options) | ||
}), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
"""Constants for the Coronavirus Hessen integration.""" | ||
DOMAIN = "coronavirus_hessen" | ||
ENDPOINT = "https://soziales.hessen.de/gesundheit/infektionsschutz/coronavirus-sars-cov-2/taegliche-uebersicht-der-bestaetigten-sars-cov-2-faelle-hessen" | ||
ATTRIBUTION = "Data provided by Hessisches Ministrium für Soziales und Integration" | ||
OPTION_TOTAL = "total" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"domain": "coronavirus_hessen", | ||
"name": "Coronavirus Hessen", | ||
"config_flow": true, | ||
"documentation": "https://github.com/foosel/homeassistant-coronavirus-hessen", | ||
"requirements": ["beautifulsoup4==4.8.2"], | ||
"dependencies": [], | ||
"codeowners": ["@foosel"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
"""Support for getting current Corona data from the website of the Hessische Ministerium für Soziales und Integration.""" | ||
import logging | ||
|
||
import voluptuous as vol | ||
|
||
from homeassistant.const import ATTR_ATTRIBUTION | ||
from homeassistant.helpers.entity import Entity | ||
|
||
from . import get_coordinator | ||
from .const import ATTRIBUTION, OPTION_TOTAL | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
async def async_setup_entry(hass, config_entry, async_add_entities): | ||
"""Defer sensor setup to the shared sensor module.""" | ||
coordinator = await get_coordinator(hass) | ||
|
||
async_add_entities([CoronaHessenSensor(coordinator, config_entry.data["county"])]) | ||
|
||
class CoronaHessenSensor(Entity): | ||
"""Representation of a county with Corona cases.""" | ||
|
||
def __init__(self, coordinator, county): | ||
"""Initialize sensor.""" | ||
self.coordinator = coordinator | ||
self.county = county | ||
if county == OPTION_TOTAL: | ||
self._name = f"Coronavirus Hessen" | ||
else: | ||
self._name = f"Coronavirus Hessen {county}" | ||
self._state = None | ||
|
||
@property | ||
def available(self): | ||
return self.coordinator.last_update_success and self.county in self.coordinator.data | ||
|
||
@property | ||
def name(self): | ||
return self._name | ||
|
||
@property | ||
def icon(self): | ||
return "mdi:biohazard" | ||
|
||
@property | ||
def unit_of_measurement(self): | ||
return "people" | ||
|
||
@property | ||
def state(self): | ||
return self.coordinator.data[self.county] | ||
|
||
@property | ||
def device_state_attributes(self): | ||
return {ATTR_ATTRIBUTION: ATTRIBUTION} | ||
|
||
async def async_added_to_hass(self): | ||
"""When entity is added to hass.""" | ||
self.coordinator.async_add_listener(self.async_write_ha_state) | ||
|
||
async def async_will_remove_from_hass(self): | ||
"""When entity will be removed from hass.""" | ||
self.coordinator.async_remove_listener(self.async_write_ha_state) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"config": { | ||
"title": "Coronavirus Hessen", | ||
"step": { | ||
"user": { | ||
"title": "Pick a county to monitor", | ||
"data": { | ||
"country": "County" | ||
} | ||
} | ||
}, | ||
"abort": { | ||
"already_configured": "This county is already configured." | ||
} | ||
} | ||
} |