Skip to content

Commit

Permalink
Merge pull request #3 from FaserF/feature/config-flow
Browse files Browse the repository at this point in the history
Add support for HA config Flow
  • Loading branch information
FaserF authored Sep 30, 2022
2 parents e6b9b92 + cb96d29 commit 07d2e3f
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 94 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ The `deutschebahn` sensor will give you the departure time of the next train for

The official Deutsche Bahn Homeassistant integration got removed with release 2022.11 - therefore this custom integration exists. It got removed due to cloud scraping, but still was fully functional.

I will not give much support on this integration, only keeping the dependencys up to date! Use it as it is, this is the same part as of release 2022.09 from HA, see [here](https://github.com/home-assistant/core/tree/c741d9d0452970c39397deca1c65766c8cb917da/homeassistant/components/deutsche_bahn).
Please note that I will only give limited support on this integration

This integration is based on the formerly officia HA DB integration, see [here](https://github.com/home-assistant/core/tree/c741d9d0452970c39397deca1c65766c8cb917da/homeassistant/components/deutsche_bahn).

This sensor stores a lot of attributes which can be accessed by other sensors, e.g., a [template sensor](https://www.home-assistant.io/integrations/template/).

Expand All @@ -35,13 +37,17 @@ where `<config>` is your Home Assistant configuration directory.
>__NOTE__: Do not download the file by using the link above directly, the status in the "master" branch can be in development and therefore is maybe not working.
## Configuration
Go to Configuration -> Integrations and click on "add integration". Then search for Rewe.

### Configuration Variables
- **from**: The name of the start station.
- **to**: The name of the end/destination station.
- **offset** (optional): Do not display departures leaving sooner than this number of seconds. Useful if you are a couple of minutes away from the stop. The formats "HH:MM" and "HH:MM:SS" are also supported.
- **only_direct** (optional - default is false): Only show direct connections.

### YAML Config
DEPRECATED, use the GUI setup instead!

```yaml
# Example configuration.yaml entry
sensor:
Expand Down Expand Up @@ -75,5 +81,6 @@ logger:
## Thanks to
Huge thanks to [@homeassistant](https://github.com/home-assistant/core/tree/c741d9d0452970c39397deca1c65766c8cb917da/homeassistant/components/deutsche_bahn) for the official old integration, where this one is based on!
Also to [@kennell](https://github.com/kennell/schiene) for the schiene python library that this integration is using.
The data is coming from the [bahn.de](https://www.bahn.de/p/view/index.shtml) website.
85 changes: 84 additions & 1 deletion custom_components/deutschebahn/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,84 @@
"""The deutschebahn component."""
"""Bahn.de Custom Component."""
import asyncio
import logging
from datetime import timedelta

from homeassistant import config_entries, core

from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(minutes=2)

async def async_setup_entry(
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
) -> bool:
"""Set up platform from a ConfigEntry."""
hass.data.setdefault(DOMAIN, {})
hass_data = dict(entry.data)
# Registers update listener to update config entry when options are updated.
unsub_options_update_listener = entry.add_update_listener(options_update_listener)
# Store a reference to the unsubscribe function to cleanup if an entry is unloaded.
hass_data["unsub_options_update_listener"] = unsub_options_update_listener
hass.data[DOMAIN][entry.entry_id] = hass_data

# Forward the setup to the sensor platform.
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "sensor")
)

config = hass.data[DOMAIN][entry.entry_id]
async def async_update_data():
"""Fetch data from Schiene."""
update_interval=timedelta(minutes=config[CONF_SCAN_INTERVAL])
async with async_timeout.timeout(update_interval - 1):
await hass.async_add_executor_job(lambda: data.update())

if not data.state:
raise UpdateFailed(f"Error fetching {entry.entry_id} Schiene state")

return data.state

coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name=f"{entry.entry_id} Schiene state",
update_method=async_update_data,
)

return True


async def options_update_listener(
hass: core.HomeAssistant, config_entry: config_entries.ConfigEntry
):
"""Handle options update."""
await hass.config_entries.async_reload(config_entry.entry_id)

async def async_update(self):
"""Async wrapper for update method."""
return await self._hass.async_add_executor_job(self._update)

async def async_unload_entry(
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
) -> bool:
"""Unload a config entry."""
unload_ok = all(
await asyncio.gather(
*[hass.config_entries.async_forward_entry_unload(entry, "sensor")]
)
)
# Remove options_update_listener.
hass.data[DOMAIN][entry.entry_id]["unsub_options_update_listener"]()

# Remove config entry from domain.
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok
97 changes: 97 additions & 0 deletions custom_components/deutschebahn/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Config flow"""
import logging

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv

from .const import ( # pylint: disable=unused-import
CONF_DESTINATION,
CONF_START,
CONF_OFFSET,
CONF_ONLY_DIRECT,
)
DOMAIN = "deutschebahn"

_LOGGER = logging.getLogger(__name__)


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow"""

VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL

async def async_step_user(self, user_input=None):
"""Handle the initial step."""
errors = {}

if user_input is not None:
await self.async_set_unique_id(user_input[CONF_START])
self._abort_if_unique_id_configured()
return self.async_create_entry(title=user_input[CONF_START] + "-" + user_input[CONF_DESTINATION], data=user_input)

_LOGGER.debug(
"Initialized new deutschebahn with id: {unique_id}"
)

data_schema = vol.Schema(
{
vol.Required(CONF_START): str,
vol.Required(CONF_DESTINATION): str,
vol.Required(CONF_OFFSET, default=0): cv.positive_int,
vol.Required(CONF_ONLY_DIRECT, default=False): cv.boolean,
},
)

return self.async_show_form(
step_id="user", data_schema=data_schema, errors=errors
)


def validate_options(user_input, errors):
"""Validate the options in the OptionsFlow.
This is an extra validation step because the validators
in `EXTRA_VALIDATION` cannot be serialized to json.
"""
for key, (validate, _) in EXTRA_VALIDATION.items():
# these are unserializable validators
value = user_input.get(key)
try:
if value is not None and value != NONE_STR:
validate(value)
except vol.Invalid:
_LOGGER.exception("Configuration option %s=%s is incorrect", key, value)
errors["base"] = "option_error"


class OptionsFlowHandler(config_entries.OptionsFlow):
"""Handle a option flow"""

def __init__(self, config_entry: config_entries.ConfigEntry):
"""Initialize options flow."""
self.config_entry = config_entry

async def async_step_init(self, user_input=None):
"""Handle options flow."""
conf = self.config_entry
if conf.source == config_entries.SOURCE_IMPORT:
return self.async_show_form(step_id="init", data_schema=None)
errors = {}
if user_input is not None:
validate_options(user_input, errors)
if not errors:
return self.async_create_entry(title="", data=user_input)

options_schema = {}
for name, default, validation in VALIDATION_TUPLES:
key = vol.Optional(name, default=conf.options.get(name, default))
value = to_replace.get(name, validation)
options_schema[key] = value

return self.async_show_form(
step_id="init", data_schema=vol.Schema(options_schema), errors=errors
)
10 changes: 10 additions & 0 deletions custom_components/deutschebahn/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""German Train System - Deutsche Bahn"""
DOMAIN = "deutschebahn"
ATTRIBUTION = "Data provided by bahn.de api"

CONF_DESTINATION = "to"
CONF_START = "from"
CONF_OFFSET = "offset"
CONF_ONLY_DIRECT = "only_direct"

ATTR_DATA = "data"
4 changes: 2 additions & 2 deletions custom_components/deutschebahn/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
"codeowners": [
"@faserf"
],
"config_flow": false,
"config_flow": true,
"documentation": "https://github.com/faserf/ha-deutschebahn#readme",
"issue_tracker": "https://github.com/faserf/ha-deutschebahn/issues",
"requirements": [
"schiene==0.26"
],
"version": "1.0.2",
"version": "2.0.0",
"iot_class": "cloud_polling",
"loggers": [
"schiene"
Expand Down
Loading

0 comments on commit 07d2e3f

Please sign in to comment.