Skip to content

About

Home Assistant Integration for UL Public Transit (Sweden)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

16 Commits

Folders and files

Repository files navigation

UL Transport for Home Assistant

HACS Custom GitHub release Validate License: MIT

Real-time departures for UL (Upplands Lokaltrafik) in Uppsala lΓ€n, in Home Assistant. Get a "next bus in 4 minutes" sensor for any stop, a live map of the buses actually heading your way, and a voice answer when you ask.

The live map card, showing buses inbound to Vaksala torg in Uppsala

What you get

🚌 A sensor per stop and line Its state is a plain number of minutes, so numeric_state triggers work with no templating
πŸ—ΊοΈ A live map card Real vehicle positions, arrival times and how many stops away each bus is
πŸ•‘ A departure board The same card as a list, cheap enough to leave on a wall panel all day
πŸ—£οΈ A voice answer "When does the next bus go?" β€” answered by your Assist agent
πŸ” Setup in the UI Search for your stop by name; no YAML, no stop ids to look up
πŸ‡ΈπŸ‡ͺ English and Swedish Both fully translated

The departure sensors work on their own. The live map is optional and needs two free API keys β€” see Live map card.

Requirements

  • Home Assistant 2025.2 or newer
  • A stop served by UL (Uppsala county)
  • For the live map only: a free Trafiklab account

Installation

HACS

  1. HACS β†’ three-dot menu β†’ Custom repositories
  2. Add https://github.com/AlexanderBabel/ul-transport with category Integration
  3. Search for UL Transport, install it, and restart Home Assistant

Manually

Copy custom_components/ul_transport into your Home Assistant config/custom_components directory and restart.

Setup

Settings β†’ Devices & Services β†’ + Add Integration, search for UL Transport. The first run asks one question:

  • Set up the live map β€” enter two free Trafiklab keys, once, for the whole install. See Live map card.
  • Skip β€” go straight to adding a stop. Departure sensors need no keys, and you can add the map later.

Adding a stop

  1. Type part of your stop's name β€” "Centralstationen", "GrΓ€nby", whatever you would say out loud
  2. Pick your stop from the results
  3. Choose which lines to follow. Leave it empty for all of them, which also covers lines that only run at other times of day
  4. Set how often to refresh (default 60 s, range 30–600 s)

Searching for a stop by name in the setup dialog

Repeat for as many stops as you like β€” each becomes its own device, and after the first run the integration goes straight to this search. To change lines or the refresh interval later, press Configure on that stop.

Entities

Each stop is a device, and its entities are grouped under it. Rename the device and the entity names follow.

Entity What its state is
sensor.<stop>_next_departure Minutes until the next departure, whichever line
sensor.<stop>_line_<line>_to_<direction> Minutes until that line's next departure
sensor.<stop>_last_update When the board was last fetched (diagnostic)
button.<stop>_refresh Fetches the board by hand

Attributes on the departure sensors: line, direction, transport, stop_name, departure (real time where there is any, otherwise planned), scheduled_departure, delay_minutes (positive is late, null when the bus reports no real-time data at all), is_realtime, and next_departures / next_departures_in for the four after it.

The state only moves when the board is polled, so it lags by up to your refresh interval. For a countdown that ticks, use the map card.

An automation

Because the state is a number of minutes, this is all it takes:

automation:
  - alias: Nudge me before the bus
    triggers:
      - trigger: numeric_state
        entity_id: sensor.vaksala_torg_next_departure
        below: 6
    actions:
      - action: notify.mobile_app_phone
        data:
          message: >
            Line {{ state_attr('sensor.vaksala_torg_next_departure', 'line') }}
            to {{ state_attr('sensor.vaksala_torg_next_departure', 'direction') }}
            leaves in {{ states('sensor.vaksala_torg_next_departure') }} minutes.

Live map card

The map shows the buses actually heading to your stop β€” where they are, when they will arrive, and how many stops away they are. It creates no entities and fetches only while a card is on screen, so nothing is polled when nobody is looking and no vehicle data reaches your recorder.

1. Get two free Trafiklab keys

UL's own departure API publishes times but not vehicle positions or route geometry, so the map needs a second source. Both datasets are free.

  1. Register at trafiklab.se and create a project
  2. Add two datasets to it, and copy the key for each:
    • GTFS Regional β€” line names, stop names, route geometry
    • GTFS Regional Realtime β€” vehicle positions and arrival predictions
  3. In Home Assistant, add the integration and choose Set up the live map, then paste both keys

They are checked against Trafiklab before being saved, so a key pasted into the wrong box is caught there rather than by the map silently never appearing.

The keys live in their own Trafiklab API entry, separate from your stops. You enter them once for the whole install, every stop uses them, and removing a stop leaves them alone. To change them later, press Configure on that entry.

Already have stops but no map? Add the integration again β€” the live map option is offered whenever the keys are not set up yet.

The first load downloads a ~26 MB timetable and builds an index β€” a few seconds β€” then refreshes quietly every 6 hours.

2. Add the card

Open a dashboard, + Add card, and pick UL Transport Live Map. It has a live preview and a visual editor, so there is no YAML to write: choose the stop, tick the lines you care about, and drag the radius slider until the map frames what you want.

The YAML equivalent is two lines:

type: custom:ul-transport-map
stop_id: 3700062

Clicking a bus, or an arrival row, switches to that line's route view:

One line's whole route, with every bus running on it

A cheap departure board

content: list drops the map and polls once every 90 seconds instead of every 5 β€” cheap enough to leave up on a wall panel all day. The countdowns still tick down by the second in between, because the browser works them out from the departure times.

type: custom:ul-transport-map
stop_id: 3700062
content: list
list_minutes: 1440
list_count: 20

The same card as a departure board, with no map

Past about forty minutes the realtime feed runs dry, so the list continues from the timetable β€” list_minutes: 1440 turns it into a board for the whole service day at no extra request.

Everything else

There are 20 card options β€” filtering by line or destination, layout, refresh rate, how far ahead to look, animation, tap actions. All of them have a control in the visual editor.

β†’ Full live map reference β€” every option, what each row means, how far behind reality the map is, and the API quota.

Watch your quota

Trafiklab's free tier allows 30,000 realtime requests per 30 days. An open map spends about a day's worth per hour, so it is for looking at rather than for leaving up; the list card above fits comfortably inside the quota.

Two diagnostic sensors on a Trafiklab API device track exactly where you stand:

Entity Limit
sensor.trafiklab_api_realtime_requests_30_days 30,000
sensor.trafiklab_api_static_requests_30_days 60

They count over the same trailing 30 days the quota does, and survive restarts. The integration stops calling out once the count is reached, so the card goes stale rather than your key getting blocked. More on the quota.

Ask your voice assistant

The integration exposes UL Transport departures as an Assist LLM API, so a conversation agent can answer out loud.

Settings β†’ Devices & Services β†’ your conversation agent β†’ Configure, then tick UL Transport departures under the exposed APIs.

The agent gets one tool, get_next_departures, taking an optional stop and line. Stop names are matched loosely, because speech-to-text gives "Vaksala torg" for a stop configured as "Uppsala Vaksala torg".

If you use the Voice Satellite card (2026.8.10 or later), the answer brings the live map up on the satellite's screen while it is read out. Nothing to configure on either side.

Troubleshooting

No stops found. Try a shorter search term, and check the spelling β€” the search matches UL's own names.

A line I expected is missing. Sensors are only created for lines running at the moment the stop is set up. Leave the line selection empty to follow every line, including ones that only run in the morning.

"Custom element doesn't exist: ul-transport-map". The card registers itself as a dashboard resource, which browsers pick up the next time they open a dashboard. A tab left open across an upgrade β€” usually a wall panel β€” needs one hard reload. If it still does not appear, check that Settings β†’ Dashboards β†’ Resources lists /ul_transport/ul-transport-map.js.

The map never loads. Check that a Trafiklab API entry exists under Devices & Services and holds a key for each of the two different datasets. If sensor.trafiklab_api_static_requests_30_days is 0, the timetable was never downloaded.

Rate limit errors from UL. Raise the refresh interval, and wait a few minutes between configuration changes.

Still stuck? Download diagnostics from the integration's three-dot menu β€” they include what UL is advertising versus what you selected, which is the usual culprit β€” and open an issue. The keys are redacted.

Removing it

Delete the stops from Settings β†’ Devices & Services, and the Trafiklab API entry if you set one up. The card's dashboard resource is removed with the last entry. Then uninstall from HACS.

Documentation

Contributing

Bug reports, ideas and pull requests are all welcome β€” this is a small integration and easy to work on.

python -m venv venv && ./venv/bin/pip install -r requirements_test.txt

Run the checks the way CI does:

pytest
ruff check custom_components tests && ruff format --check custom_components tests

Every push also runs Home Assistant's own hassfest and HACS validation β€” see .github/workflows/validate.yml.

A few things worth knowing before you start:

  • The card is shipped as plain JavaScript, no build step. It has to parse on Home Assistant's legacy browser bundle, so no optional chaining and no nullish coalescing; tests/test_card_asset.py enforces this.
  • Comments explain why, not what. Most of the surprising code is working around something real in UL's feeds, and the comment is the evidence for it.
  • Keep examples on a public hub. Vaksala torg (3700062) and Uppsala Centralstation (3700600) are the ones used throughout; don't swap in a residential stop, in docs, tests or screenshots.
  • New behaviour wants a test; pytest --cov=custom_components/ul_transport --cov-report=term-missing shows where the gaps are.

To run against a real instance, put the repo's custom_components/ul_transport in a test config's custom_components directory and point Home Assistant at it.

Credits

Built by @AlexanderBabel. Departure data by UL; vehicle positions and timetables via Trafiklab / Samtrafiken. Maps by OpenStreetMap contributors, drawn with Leaflet.

Not affiliated with or endorsed by UL. MIT licensed β€” see LICENSE.

About

Home Assistant Integration for UL Public Transit (Sweden)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages