A Home Assistant integration for the public transport networks VRR (Verkehrsverbund Rhein-Ruhr), KVV (Karlsruher Verkehrsverbund), HVV (Hochbahn), Trafiklab (Sweden) and NTA (National Transport Authority, Ireland). This integration provides real-time departure information for public transport in NRW, Karlsruhe, Hamburg, Sweden and Ireland.
- Smart Setup Wizard: Intuitive multi-step configuration with autocomplete for locations and stops
- Real-time Departures: Shows current departure times with delays
- Multiple Transport Types: Supports trains (ICE, IC, RE), subway, trams, and buses
- Smart Filtering: Filter by specific transportation types
- Binary Sensor for Delays: Automatic detection of delays > 5 minutes
- Device Support: Entities are grouped together with suggested areas
- Repair Issues Integration: Automatic notifications for API errors or rate limits
- Rate Limiting: Intelligent API rate limiting to prevent overload (60,000 calls/day)
- Error Handling: Robust error handling with exponential backoff strategy
- Timezone Support: Proper handling of provider-specific timezones (Europe/Berlin for VRR/KVV/HVV, Europe/Stockholm for Trafiklab, Europe/Dublin for NTA)
- Fuzzy Matching with Typo Tolerance: Intelligently finds stops even with typos
- Handles common misspellings: "Hauptbanhof" → "Hauptbahnhof"
- German umlaut normalization: "Dusseldorf" → "Düsseldorf"
- Multi-level scoring using SequenceMatcher and Levenshtein distance
- Smart result ranking based on relevance
- API Response Caching: 5-minute intelligent cache reduces API load
- Instant results for repeated searches
- Automatic cache management (LRU-like eviction)
- Normalized cache keys for better hit rate
- Optimized Sensor Performance: 20-30% faster departure processing
- Reduced coordinator lookups
- O(1) set-based filtering instead of O(n) lists
- Single-pass processing for statistics
- Enhanced Code Quality: Full type hints, comprehensive docstrings, 75% test coverage
- Open HACS in Home Assistant
- Go to "Integrations"
- Click the three dots in the top right and select "Custom repositories"
- Add this repository URL:
https://github.com/nerdysoftpaw/VRRAPI-HACS - Select "Integration" as category
- Click "Add"
- Search for "Public" and install the integration
- Restart Home Assistant
- Copy the
custom_components/public_transport_defolder to yourcustom_componentsdirectory - Restart Home Assistant
The integration uses an intuitive multi-step setup wizard with autocomplete functionality:
-
Select Provider
- Choose between VRR (NRW), KVV (Karlsruhe), HVV (Hamburg), Trafiklab (Sweden) or NTA (Ireland)
- For Trafiklab: A free API key from trafiklab.se is required
- For NTA: A free API key from developer.nationaltransport.ie is required
-
Search City/Location
- Enter your city (e.g. "Düsseldorf", "Köln", "Hamburg")
- The integration automatically searches for matching locations
-
Select Stop
- Enter the name of your stop (e.g. "Hauptbahnhof", "Marktplatz")
- The integration automatically suggests stops in your location
-
Configure Settings
- Number of departures (1-20)
- Transport type filter (Bus, Train, Tram, etc.)
- Update interval (10-3600 seconds)
- Go to Settings > Devices & Services
- Click + Add Integration
- Search for "VRR" or "Public Transport Departures"
- Follow the setup wizard
For the Trafiklab provider (Sweden), you need a free API key:
- Register at trafiklab.se
- Create a new project
- Select the "Realtime API"
- Copy the API key
- Enter it in the integration's Config Flow
Note: The API key is only required for Trafiklab sensors. No API key is required for VRR, KVV and HVV.
For the NTA provider (Ireland), you need a free API key:
- Register at developer.nationaltransport.ie
- Subscribe to the "GTFS-Realtime" API
- You will receive a Primary and Secondary API key
- Enter the Primary API key in the integration's Config Flow (required)
- Optionally enter the Secondary API key (used as fallback if Primary fails)
Note:
- The Primary API key is required for NTA sensors
- The Secondary API key is optional but recommended as a fallback
- NTA uses GTFS-RT (General Transit Feed Specification - Realtime) format
- GTFS Static data is automatically downloaded for stop search functionality
train- Trains (ICE, IC, RE, RB)subway- Subway/Metro (U-Bahn)tram- Tram/Streetcarbus- Busferry- Ferrytaxi- Taxi
After installation, add the integration via UI:
- Go to Settings > Devices & Services
- Click "+ Add Integration"
- Search for "VRR" or "Public Transport Departures"
- Follow the setup wizard
type: entities
title: Düsseldorf Hauptbahnhof
entities:
- entity: sensor.vrr_dusseldorf_hauptbahnhof
name: Nächste Abfahrt
- type: attribute
entity: sensor.vrr_dusseldorf_hauptbahnhof
attribute: next_departure_minutes
name: In Minuten
suffix: min
- type: attribute
entity: sensor.vrr_dusseldorf_hauptbahnhof
attribute: total_departures
name: Verfügbare Verbindungentype: markdown
title: Abfahrten - Hauptbahnhof
content: >
{% set departures = state_attr('sensor.vrr_dusseldorf_hauptbahnhof',
'departures') %}
{% if departures %}
{% for departure in departures[:5] %}
**{{ departure.line }}** → {{ departure.destination }}
🕐 {{ departure.departure_time }} {% if departure.delay > 0 %}(+{{ departure.delay }} min){% endif %}
📍 Gleis {{ departure.platform }}
{% endfor %}
{% else %}
Keine Abfahrten verfügbar
{% endif %}type: button
name: Abfahrten aktualisieren
icon: mdi:refresh
tap_action:
action: call-service
service: vrr.refresh_departures
service_data:
entity_id: sensor.vrr_dusseldorf_hauptbahnhoftype: custom:auto-entities
card:
type: entities
title: Alle Abfahrten
filter:
template: >
{% set departures = state_attr('sensor.vrr_dusseldorf_hauptbahnhof',
'departures') %}
{% for departure in departures %}
{{
{
'type': 'custom:template-entity-row',
'name': departure.line + ' → ' + departure.destination,
'icon': 'mdi:train',
'state': departure.departure_time,
'secondary': 'Gleis ' + departure.platform + ' | in ' + departure.minutes_until_departure|string + ' min'
}
}},
{% endfor %}Add this to your configuration.yaml:
template:
- sensor:
- name: "Next Train Minutes"
state: >
{{ state_attr('sensor.vrr_dusseldorf_hauptbahnhof', 'next_departure_minutes') }}
unit_of_measurement: "min"
icon: mdi:clock-outline
- name: "Next Train Line"
state: >
{% set departures = state_attr('sensor.vrr_dusseldorf_hauptbahnhof', 'departures') %}
{% if departures and departures|length > 0 %}
{{ departures[0].line }}
{% else %}
-
{% endif %}
icon: mdi:traintype: conditional
conditions:
- entity: sensor.next_train_minutes
state_not: unavailable
state_not: unknown
- entity: sensor.next_train_minutes
state_below: 10
card:
type: markdown
content: >
⚠️ **Achtung!** Dein Zug fährt in {{ states('sensor.next_train_minutes') }} Minuten!type: vertical-stack
cards:
- type: markdown
title: 🚉 Düsseldorf Hauptbahnhof
content: >
Nächste Abfahrt: **{{ states('sensor.vrr_dusseldorf_hauptbahnhof') }}**
In {{ state_attr('sensor.vrr_dusseldorf_hauptbahnhof', 'next_departure_minutes') }} Minuten
- type: custom:mushroom-chips-card
chips:
- type: entity
entity: sensor.vrr_dusseldorf_hauptbahnhof
icon: mdi:train
content_info: state
- type: template
icon: mdi:clock-outline
content: >
{{ state_attr('sensor.vrr_dusseldorf_hauptbahnhof', 'next_departure_minutes') }} min
- type: template
icon: mdi:refresh
tap_action:
action: call-service
service: vrr.refresh_departures
- type: markdown
content: >
{% set departures = state_attr('sensor.vrr_dusseldorf_hauptbahnhof', 'departures') %}
{% if departures %}
| Linie | Ziel | Abfahrt | Gleis |
|-------|------|---------|-------|
{% for dep in departures[:5] %}
| **{{ dep.line }}** | {{ dep.destination }} | {{ dep.departure_time }}{% if dep.delay > 0 %} <font color="red">(+{{ dep.delay }})</font>{% endif %} | {{ dep.platform }} |
{% endfor %}
{% endif %}Manually refresh departure data from the API.
service: vrr.refresh_departures
data:
entity_id: sensor.vrr_dusseldorf_hauptbahnhof # OptionalExamples:
Refresh all VRR sensors:
service: vrr.refresh_departuresRefresh specific sensor:
service: vrr.refresh_departures
data:
entity_id: sensor.vrr_dusseldorf_hauptbahnhofUse in automation:
automation:
- alias: "Refresh departures when arriving home"
trigger:
- platform: state
entity_id: person.john
to: home
action:
- service: vrr.refresh_departures
data:
entity_id: sensor.vrr_dusseldorf_hauptbahnhofThe integration implements intelligent rate limiting:
- Daily Limit: 800 API calls per day (with buffer)
- Retry Logic: Exponential backoff on errors
- Timeout: 10 seconds per API call
- Max Retries: 3 attempts per update
The integration supports Home Assistant's diagnostics feature for easier troubleshooting.
- Go to Settings > Devices & Services
- Find your VRR integration
- Click on the integration
- Click the 3 dots menu
- Select Download Diagnostics
The diagnostics file contains:
- Configuration details (anonymized)
- Coordinator status
- API call statistics
- Sample API response structure
- Last update information
This information is helpful when reporting issues on GitHub.
To find the station ID, use the VRR API:
https://openservice-test.vrr.de/static03/XML_STOPFINDER_REQUEST?outputFormat=RapidJSON&locationServerActive=1&type_sf=stop&name_sf=Düsseldorf%20Hauptbahnhof
logger:
default: warning
logs:
custom_components.vrr: debug-
"No departures" State:
- Check station ID or place_dm/name_dm
- Verify the stop exists
-
API Rate Limit Reached:
- Increase scan_interval
- Reduce number of VRR sensors
-
Unknown Transportation Types:
- Check debug logs for new product.class values
- Report missing mappings as an issue
The integration maps VRR API Product Classes:
| Class | Transport Type | Description |
|---|---|---|
| 0, 1 | train | Legacy trains |
| 2, 3 | subway | Subway/Metro (U-Bahn) |
| 4 | tram | Tram/Streetcar |
| 5-8, 11 | bus | Various bus types |
| 9 | ferry | Ferry |
| 10 | taxi | Taxi |
| 13 | train | Regional train (RE) |
| 15 | train | InterCity (IC) |
| 16 | train | InterCityExpress (ICE) |
This integration includes a comprehensive test suite to ensure quality and reliability.
-
Install test dependencies:
pip install -r requirements_test.txt
-
Run the test suite:
pytest
-
Run tests with coverage report:
pytest --cov=custom_components/vrr --cov-report=html
-
Run specific test files:
pytest tests/test_sensor.py pytest tests/test_binary_sensor.py
The test suite includes:
- Coordinator Tests: Rate limiting, API error handling, data updates
- Sensor Tests: State updates, icon changes, transportation filtering, attribute validation
- Binary Sensor Tests: Delay detection, threshold testing, icon states
- Config Flow Tests: User flow, options flow, validation
- Diagnostics Tests: Diagnostic data output
- Integration Tests: Setup, unload, service registration
The project uses automated code quality tools:
- Black: Code formatting
- isort: Import sorting
- Flake8: Linting
- mypy: Type checking
Run code quality checks:
black custom_components/vrr/
isort custom_components/vrr/
flake8 custom_components/vrr/
mypy custom_components/vrr/Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Write tests for new features
- Ensure all tests pass
- Run code quality checks
- Commit your changes
- Create a Pull Request
This project is licensed under the MIT License.
For issues or questions:
- Check the debug logs
- Search existing issues for similar problems
- Create a new issue with debug information
https://openservice-test.vrr.de/static03/XML_DM_REQUEST?outputFormat=RapidJSON&stateless=1&type_dm=any&name_dm=20018235&mode=direct&useRealtime=1&limit=10
https://openservice-test.vrr.de/static03/XML_DM_REQUEST?outputFormat=RapidJSON&place_dm=Düsseldorf&type_dm=stop&name_dm=Hauptbahnhof&mode=direct&useRealtime=1&limit=10
HVV (Hamburger Verkehrsverbund) is now supported!
- Use
provider: hvvin your configuration to fetch departures from any HVV stop. - Platform information is parsed from
location.properties.platformin the HVV API response. - All relevant transport types (bus, metrobus, expressbus, etc.) are mapped.
- Real-time data is shown if HVV provides it via deviations between
departureTimePlannedanddepartureTimeEstimated.
NTA (National Transport Authority, Ireland) is now supported!
- Use
provider: nta_iein your configuration to fetch real-time departures from any Irish public transport stop. - Uses GTFS-RT (General Transit Feed Specification - Realtime) API for real-time data.
- GTFS Static data is automatically downloaded and cached for stop search functionality.
- Supports all Irish transport operators: Dublin Bus, Bus Éireann, Go-Ahead Ireland, Luas, and Iarnród Éireann.
- Real-time delays are calculated from GTFS-RT trip updates.
- Stop search uses GTFS Static
stops.txtdata (no API calls needed for search).
Transport Types Supported:
- Bus (route_type 3)
- Tram/Light Rail (route_type 0, 5, 6)
- Subway/Metro (route_type 1)
- Train/Rail (route_type 2, 7)
- Ferry (route_type 4)
Example NTA Configuration:
- Select "NTA (Ireland)" as provider
- Enter your Primary API key (and optionally Secondary key)
- Search for a stop (e.g., "Dublin Connolly", "Heuston Station")
- Select your stop and configure settings
API Information:
- Base URL:
https://api.nationaltransport.ie/gtfsr - Endpoint:
/v2/TripUpdates?format=json - Authentication: API key via
x-api-keyheader - GTFS Static: Automatically downloaded from Transport for Ireland
Example HVV API response:
{
"stopEvents": [
{
"location": {
"name": "Stadionstraße",
"properties": {
"stopId": "28582004",
"platform": "1"
}
},
"departureTimePlanned": "2025-06-22T20:00:00Z",
"transportation": {
"number": "2",
"description": "Berliner Tor > Hbf. > Altona > Schenefeld",
"product": {
"class": 5,
"name": "Bus"
},
"destination": {
"name": "Schenefeld, Schenefelder Platz"
}
}
}
]
}- NTA Ireland Integration: Full support for National Transport Authority (Ireland) GTFS-RT API
- Real-time departure information for all Irish public transport operators
- Automatic GTFS Static data download and caching for stop search
- Support for Primary and Secondary API keys with automatic fallback
- GTFS route_type mapping to internal transport types (bus, tram, train, subway, ferry)
- Timezone support for Europe/Dublin
- GTFS Static Data Loader: New module for handling GTFS Static ZIP files
- Automatic download and caching of GTFS Static data
- 24-hour cache duration with automatic refresh
- Support for stops.txt, routes.txt, trips.txt, and stop_times.txt
- Efficient CSV parsing with async file operations
- Enhanced API Key Management: Support for Primary and Secondary API keys
- Automatic fallback to Secondary key if Primary fails with 401
- Config Flow support for both keys (Primary required, Secondary optional)
- GTFS-RT JSON Format: Uses JSON format instead of Protocol Buffers for easier parsing
- Stop Search: Uses GTFS Static stops.txt for fast, offline-capable stop search
- Route Information: Automatically resolves route names from GTFS Static routes.txt
- Transport Type Detection: Maps GTFS route_type (0-7) to internal types
Intelligent Fuzzy Matching for Stop Search
- Typo Tolerance: Automatically corrects minor typos in stop/station names
- Example: "Hauptbanhof" → finds "Hauptbahnhof"
- Example: "Dusseldorf" → finds "Düsseldorf" (umlaut normalization)
- Multi-Level Relevance Scoring:
- Exact match detection (+300 points)
- SequenceMatcher similarity ratio (up to +200 points for >80% match)
- Levenshtein distance for small typos (+120 points for 1-2 char difference)
- Per-word fuzzy matching (up to +75 points per word)
- Place name bonus when city is mentioned (+200 points)
- German Umlaut Normalization: ä→ae, ö→oe, ü→ue, ß→ss
- Smart Result Ranking: Best matches appear first, even with typos
API Response Caching
- 5-Minute Cache: Reduces redundant API calls for repeated searches
- Smart Cache Keys: Normalized by provider, search term, and type
- LRU-Like Eviction: Automatically maintains 20-entry cache limit
- Empty Result Caching: Prevents repeated API calls for non-existent stops
- Significant Performance Gain: Instant results for cached searches
Sensor Performance Optimizations
- Reduced Dictionary Lookups: Cache frequently accessed coordinator values
- Set-Based Filtering: O(1) lookup instead of O(n) for transport type filtering
- Parser Function Pre-Selection: Eliminate repeated conditional checks
- Single-Pass Processing: Combined departure processing and statistics calculation
- Expected Performance Gain: 20-30% faster sensor updates
- Enhanced Type Hints: Full typing coverage with
Callable,Union,Optional - Comprehensive Docstrings: Detailed documentation for all public methods
- Improved Validation:
- Type validation throughout sensor and config flow
- Better error messages with context
- Defensive programming with null checks
- Test Coverage Increase: From 34% to 75% (52 tests, all passing)
- New Test Suites:
test_fuzzy_matching.py: 15 tests for fuzzy matching algorithmstest_caching.py: 10 tests for API caching system- Updated
test_config_flow.py: 7 tests for simplified 2-step flow - All existing tests updated for Home Assistant 2025.10 compatibility
Fuzzy Matching Implementation
# Example: Searching for "Hauptbanhof" (typo)
search_term = "Hauptbanhof Dusseldorf"
# Finds: "Hauptbahnhof, Düsseldorf" with high relevance score
# Scoring breakdown:
# - Fuzzy ratio: 0.95 → +190 points
# - Levenshtein distance: 1 → +120 points
# - Word fuzzy match: "Hauptbanhof" ≈ "Hauptbahnhof" (0.91) → +68 points
# - Place match: "Dusseldorf" ≈ "Düsseldorf" → +200 points
# Total: 578 points (excellent match despite typo)Caching System
# First search: API call (takes ~200-500ms)
stops = await config_flow._search_stops("Hauptbahnhof")
# Same search within 5 minutes: Cache hit (takes <1ms)
stops = await config_flow._search_stops("Hauptbahnhof") # Instant!
# Different search: New API call
stops = await config_flow._search_stops("Stadtmitte") # API call
# Cache automatically manages:
# - TTL expiration (5 minutes)
# - Size limit (20 entries, oldest removed first)
# - Normalized keys (case-insensitive, umlaut-normalized)Performance Optimizations
# Before (multiple lookups):
for dep in departures:
station_name = f"{self.coordinator.place_dm} - {self.coordinator.name_dm}"
if dep["type"] in self.transportation_types: # O(n) list lookup
# Process...
# After (optimized):
station_name = f"{self.coordinator.place_dm} - {self.coordinator.name_dm}" # Once
transport_types_set = set(self.transportation_types) # O(1) lookup
parse_fn = self._get_parser_function() # Pre-selected
for dep in departures:
if dep["type"] in transport_types_set: # O(1) set lookup
# Process with pre-selected parser...- Smart Setup Wizard with Autocomplete: Multi-step configuration flow
- Search for locations (cities) with autocomplete via STOPFINDER API
- Search for stops/stations based on selected location
- Automatic suggestions for both locations and stops
- Support for all 3 providers (VRR, KVV, HVV)
- Comprehensive Test Suite: 50+ unit tests for all components
- GitHub Actions CI/CD: Automated testing, linting, and releases
- Enhanced Error Messages: Better German and English translations
- DataUpdateCoordinator Pattern: Modern Home Assistant best practice implementation
- Binary Sensor for Delays: Automatic delay detection (>5 minutes threshold)
- Device Support: Entities grouped together with suggested areas
- Repair Issues Integration: Notifications for API errors or rate limits
- Diagnostics Support: Download diagnostics for easier troubleshooting
- Manual Refresh Service:
vrr.refresh_departuresservice for manual updates - Dynamic Icons: Icon changes based on next departure type (bus, train, tram, etc.)
- Transportation Type Filtering: Now actually works! Filter departures by type
- Options Flow Support: Change settings without removing/re-adding integration
- Enhanced Sensor Attributes:
next_3_departures: Quick overview of upcoming departuresdelayed_count/on_time_count: Departure statisticsaverage_delay: Average delay across all departuresearliest_departure/latest_departure: Time range of departures
- Code Optimization: Eliminated ~200 lines of duplicate code
- API Response Validation: Better error handling and validation
- Scan Interval: Actually configurable now (10s - 3600s)
- Enhanced Logging: Better error messages with context
- Rate Limiting: Smarter handling of API limits (60,000 calls/day)
- Code Quality: Black, isort, Flake8, mypy integration
- Fixed transportation type filtering not working
- Fixed options not being applied
- Fixed scan interval being ignored
- Fixed missing imports and type hints
- Added comprehensive transport type mapping (ICE, IC, RE trains)
- Implemented intelligent API rate limiting
- Enhanced error handling with exponential backoff
- Added debug logging for transport classification
- Improved timezone handling for German local time
- Added support for both station ID and place/name queries
- Enhanced real-time data processing and delay calculations
- Improved sensor attributes for better usability
Made with ❤️ for the Home Assistant community