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.
| π 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.
- Home Assistant 2025.2 or newer
- A stop served by UL (Uppsala county)
- For the live map only: a free Trafiklab account
- HACS β three-dot menu β Custom repositories
- Add
https://github.com/AlexanderBabel/ul-transportwith category Integration - Search for UL Transport, install it, and restart Home Assistant
Copy custom_components/ul_transport into your Home Assistant config/custom_components
directory and restart.
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.
- Type part of your stop's name β "Centralstationen", "GrΓ€nby", whatever you would say out loud
- Pick your stop from the results
- Choose which lines to follow. Leave it empty for all of them, which also covers lines that only run at other times of day
- Set how often to refresh (default 60 s, range 30β600 s)
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.
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.
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.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.
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.
- Register at trafiklab.se and create a project
- 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
- 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.
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: 3700062Clicking a bus, or an arrival row, switches to that line's route view:
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: 20Past 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.
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.
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.
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.
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.
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.
- Full live map reference β card options, quota, limitations
- Changelog
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.txtRun the checks the way CI does:
pytestruff check custom_components tests && ruff format --check custom_components testsEvery 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.pyenforces 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-missingshows 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.
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.



