-
-
Notifications
You must be signed in to change notification settings - Fork 37.2k
Add RMV public transport sensor #15814
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
c3f61e0
Add new public transport sensor for RMV (Rhein-Main area).
cgtobi b9b59aa
Add required module.
cgtobi 41c34ea
Fix naming problem.
cgtobi f9e88b6
Add unit test.
cgtobi 221167e
Update dependency version to 0.0.5.
cgtobi 504049b
Add new requirements.
cgtobi db76dd0
Fix variable name.
cgtobi 2d9c122
Fix issues pointed out in review.
cgtobi 485ae89
Remove unnecessary code.
cgtobi 6fcc48d
Fix linter error.
cgtobi 741a711
Fix config value validation.
cgtobi 1403293
Replace minutes as state by departure timestamp. (see ##14983)
cgtobi f67f22e
More work on the timestamp. (see ##14983)
cgtobi 74117c3
Revert timestamp work until #14983 gets merged.
cgtobi 2accb78
Simplify product validation.
cgtobi 661bad1
Remove redundant code.
cgtobi 26f1227
Address code change requests.
cgtobi b714364
Address more code change requests.
cgtobi 8be8b07
Address even more code change requests.
cgtobi d21cec3
Simplify destination check.
cgtobi 4341b8a
Fix linter problem.
cgtobi 72c3b7e
Bump dependency version to 0.0.7.
cgtobi 9f29d30
Name variable more explicit.
cgtobi 844c439
Only query once a minute.
cgtobi cacfd46
Update test case.
cgtobi a9949a4
Fix config validation.
cgtobi 1642b47
Remove unneeded import.
cgtobi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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,202 @@ | ||
| """ | ||
| Support for real-time departure information for Rhein-Main public transport. | ||
|
|
||
| For more details about this platform, please refer to the documentation at | ||
| https://home-assistant.io/components/sensor.rmvtransport/ | ||
| """ | ||
| import logging | ||
|
|
||
| import voluptuous as vol | ||
|
|
||
| import homeassistant.helpers.config_validation as cv | ||
| from homeassistant.helpers.entity import Entity | ||
| from homeassistant.components.sensor import PLATFORM_SCHEMA | ||
| from homeassistant.const import (CONF_NAME, ATTR_ATTRIBUTION) | ||
|
|
||
| REQUIREMENTS = ['PyRMVtransport==0.0.7'] | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| CONF_NEXT_DEPARTURE = 'next_departure' | ||
|
|
||
| CONF_STATION = 'station' | ||
| CONF_DESTINATIONS = 'destinations' | ||
| CONF_DIRECTIONS = 'directions' | ||
| CONF_LINES = 'lines' | ||
| CONF_PRODUCTS = 'products' | ||
| CONF_TIME_OFFSET = 'time_offset' | ||
| CONF_MAX_JOURNEYS = 'max_journeys' | ||
|
|
||
| DEFAULT_NAME = 'RMV Journey' | ||
|
|
||
| VALID_PRODUCTS = ['U-Bahn', 'Tram', 'Bus', 'S', 'RB', 'RE', 'EC', 'IC', 'ICE'] | ||
|
|
||
| ICONS = { | ||
| 'U-Bahn': 'mdi:subway', | ||
| 'Tram': 'mdi:tram', | ||
| 'Bus': 'mdi:bus', | ||
| 'S': 'mdi:train', | ||
| 'RB': 'mdi:train', | ||
| 'RE': 'mdi:train', | ||
| 'EC': 'mdi:train', | ||
| 'IC': 'mdi:train', | ||
| 'ICE': 'mdi:train', | ||
| 'SEV': 'mdi:checkbox-blank-circle-outline', | ||
| None: 'mdi:clock' | ||
| } | ||
| ATTRIBUTION = "Data provided by opendata.rmv.de" | ||
|
|
||
| PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
| vol.Required(CONF_NEXT_DEPARTURE): [{ | ||
| vol.Required(CONF_STATION): cv.string, | ||
| vol.Optional(CONF_DESTINATIONS, default=[]): | ||
| vol.All(cv.ensure_list, [cv.string]), | ||
| vol.Optional(CONF_DIRECTIONS, default=[]): | ||
| vol.All(cv.ensure_list, [cv.string]), | ||
| vol.Optional(CONF_LINES, default=[]): | ||
| vol.All(cv.ensure_list, [cv.positive_int, cv.string]), | ||
| vol.Optional(CONF_PRODUCTS, default=VALID_PRODUCTS): | ||
| vol.All(cv.ensure_list, [vol.In(VALID_PRODUCTS)]), | ||
| vol.Optional(CONF_TIME_OFFSET, default=0): cv.positive_int, | ||
| vol.Optional(CONF_MAX_JOURNEYS, default=5): cv.positive_int, | ||
| vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string}] | ||
| }) | ||
|
|
||
|
|
||
| def setup_platform(hass, config, add_entities, discovery_info=None): | ||
| """Set up the RMV departure sensor.""" | ||
| sensors = [] | ||
| for next_departure in config.get(CONF_NEXT_DEPARTURE): | ||
| sensors.append( | ||
| RMVDepartureSensor( | ||
| next_departure[CONF_STATION], | ||
| next_departure.get(CONF_DESTINATIONS), | ||
| next_departure.get(CONF_DIRECTIONS), | ||
| next_departure.get(CONF_LINES), | ||
| next_departure.get(CONF_PRODUCTS), | ||
| next_departure.get(CONF_TIME_OFFSET), | ||
| next_departure.get(CONF_MAX_JOURNEYS), | ||
| next_departure.get(CONF_NAME))) | ||
| add_entities(sensors, True) | ||
|
|
||
|
|
||
| class RMVDepartureSensor(Entity): | ||
| """Implementation of an RMV departure sensor.""" | ||
|
|
||
| def __init__(self, station, destinations, directions, | ||
| lines, products, time_offset, max_journeys, name): | ||
| """Initialize the sensor.""" | ||
| self._station = station | ||
| self._name = name | ||
| self._state = None | ||
| self.data = RMVDepartureData(station, destinations, directions, lines, | ||
| products, time_offset, max_journeys) | ||
| self._icon = ICONS[None] | ||
|
|
||
| @property | ||
| def name(self): | ||
| """Return the name of the sensor.""" | ||
| return self._name | ||
|
|
||
| @property | ||
| def available(self): | ||
| """Return True if entity is available.""" | ||
| return self._state is not None | ||
|
|
||
| @property | ||
| def state(self): | ||
| """Return the next departure time.""" | ||
| return self._state | ||
|
|
||
| @property | ||
| def state_attributes(self): | ||
| """Return the state attributes.""" | ||
| try: | ||
| return { | ||
| 'next_departures': [val for val in self.data.departures[1:]], | ||
| 'direction': self.data.departures[0].get('direction'), | ||
| 'line': self.data.departures[0].get('line'), | ||
| 'minutes': self.data.departures[0].get('minutes'), | ||
| 'departure_time': | ||
| self.data.departures[0].get('departure_time'), | ||
| 'product': self.data.departures[0].get('product'), | ||
| } | ||
| except IndexError: | ||
| return {} | ||
|
|
||
| @property | ||
| def icon(self): | ||
| """Icon to use in the frontend, if any.""" | ||
| return self._icon | ||
|
|
||
| @property | ||
| def unit_of_measurement(self): | ||
| """Return the unit this state is expressed in.""" | ||
| return "min" | ||
|
|
||
| def update(self): | ||
| """Get the latest data and update the state.""" | ||
| self.data.update() | ||
| if not self.data.departures: | ||
| self._state = None | ||
| self._icon = ICONS[None] | ||
| return | ||
| if self._name == DEFAULT_NAME: | ||
| self._name = self.data.station | ||
| self._station = self.data.station | ||
| self._state = self.data.departures[0].get('minutes') | ||
| self._icon = ICONS[self.data.departures[0].get('product')] | ||
|
|
||
|
|
||
| class RMVDepartureData: | ||
| """Pull data from the opendata.rmv.de web page.""" | ||
|
|
||
| def __init__(self, station_id, destinations, directions, | ||
| lines, products, time_offset, max_journeys): | ||
| """Initialize the sensor.""" | ||
| import RMVtransport | ||
| self.station = None | ||
| self._station_id = station_id | ||
| self._destinations = destinations | ||
| self._directions = directions | ||
| self._lines = lines | ||
| self._products = products | ||
| self._time_offset = time_offset | ||
| self._max_journeys = max_journeys | ||
| self.rmv = RMVtransport.RMVtransport() | ||
| self.departures = [] | ||
|
|
||
| def update(self): | ||
| """Update the connection data.""" | ||
| try: | ||
| _data = self.rmv.get_departures(self._station_id, | ||
| products=self._products, | ||
| maxJourneys=50) | ||
| except ValueError: | ||
| self.departures = [] | ||
| _LOGGER.warning("Returned data not understood") | ||
| return | ||
| self.station = _data.get('station') | ||
| _deps = [] | ||
| for journey in _data['journeys']: | ||
| # find the first departure meeting the criteria | ||
| _nextdep = {ATTR_ATTRIBUTION: ATTRIBUTION} | ||
| if self._destinations: | ||
| dest_found = False | ||
| for dest in self._destinations: | ||
| if dest in journey['stops']: | ||
| dest_found = True | ||
| _nextdep['destination'] = dest | ||
| if not dest_found: | ||
| continue | ||
| elif self._lines and journey['number'] not in self._lines: | ||
| continue | ||
| elif journey['minutes'] < self._time_offset: | ||
| continue | ||
| for attr in ['direction', 'departure_time', 'product', 'minutes']: | ||
| _nextdep[attr] = journey.get(attr, '') | ||
| _nextdep['line'] = journey.get('number', '') | ||
| _deps.append(_nextdep) | ||
| if len(_deps) > self._max_journeys: | ||
| break | ||
| self.departures = _deps | ||
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
productsis weird for me, maybevehicle?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
productis the wording taken from the providers public API and it is their product so it made sense to me to keep the wording the same. Maybe I should add some comments.