Live Perth (Western Australia) bus and train departures as native Home Assistant entities — with realtime delay data. Add your actual commute through the UI — Edgewater → Perth, not just "Edgewater" — and get auto-updating sensors that ignore the trains going the other way, plus a delay-aware "time to leave" alert. Zero YAML.
Built on aiotransperth.
Successor to the PyScript-based
transperth_bus_times.
- In HACS: ⋮ → Custom repositories → add
https://github.com/Chris112/ha-transperthas an Integration. - Download Transperth, then restart Home Assistant.
- Settings → Devices & Services →
+ ADD INTEGRATION(bottom right) → search Transperth.
Step 3 is easy to miss: downloading in HACS only copies the files, it doesn't
configure anything, and no setup dialog appears on its own. If Transperth
isn't in the search list after restarting, hard-refresh your browser
(Ctrl+Shift+R) — Home Assistant caches that list.
Repeat step 3 for each stop or journey you want.
Pick Bus stop or Train station:
- Bus stop — enter the stop code (the number on the stop sign). The stop is validated live and its name confirmed. Then optionally tick routes to track and set your walk time to the stop.
- Train station — pick the line, then the station you board at and, optionally, where you travel to. Both dropdowns are offline and in route order, so setup works at 3am with nothing running.
A station like Edgewater has trains going both ways, and "the next train" in either direction is rarely what you want. So a train entry is a journey leg:
Edgewater Stn → Perth Stn only city-bound trains
Perth Stn → Edgewater Stn the trip home, as a second entry
Only services that actually reach your destination count. A Clarkson short-working shows up if you're going to Joondalup and is filtered out if you're going to Butler.
Leave Travelling to empty and you get a plain station entry instead, with one sensor per direction — useful for a departure-board dashboard.
Change your walk time any time via Configure. Which way you travel is part of the entry's identity, so add a second entry for the return trip.
Each configured place becomes a device with:
| Entity | Type | Notes |
|---|---|---|
| Next train | timestamp sensor | Journey entries. Dashboards render it as "in 7 minutes" natively. Attributes: destination, platform, cars, delay_minutes, is_live. |
| Next train towards terminus | timestamp sensor | Station entries — one per direction. |
| Next departure | timestamp sensor | Bus entries. |
| Departures | sensor | Next 5 departures as a list attribute for board cards; filtered to your direction on a journey entry. |
| Next route | timestamp sensor | One per tracked bus route. |
| Time to leave | binary sensor | On when now ≥ departure − walk time, using the live estimate — a train running 3 minutes late delays the nudge by 3 minutes. Flips punctually, not on the next poll. Bus entries get one per tracked route; train entries get one once they name a destination. |
| Status | diagnostic sensor | When Transperth was last reached for this data — entries sharing a station agree, even though they poll separately. rate_limited attribute. |
Timestamps use the live estimate when a vehicle is tracked, otherwise the schedule. Trains are always live; buses light up when the vehicle is on the road (typically the imminent departure).
automation:
- alias: "Leave for the 414"
triggers:
- trigger: state
entity_id: binary_sensor.main_st_after_royal_st_12627_time_to_leave_for_the_414
to: "on"
conditions:
- condition: time
after: "07:00:00"
before: "09:00:00"
weekday: [mon, tue, wed, thu, fri]
actions:
- action: notify.mobile_app_your_phone
data:
title: "Leave now"
message: >
The 414 departs at
{{ states('sensor.main_st_after_royal_st_12627_next_414') | as_timestamp | timestamp_custom('%H:%M') }}.Ad-hoc queries for places you haven't configured — all return response data:
transperth.get_bus_departures—stop_code, optionalattransperth.get_bus_schedule—stop_code,bus_number, optionalattransperth.get_bus_stops—bus_number, optionalat(full stop list of the next trip)transperth.get_train_departures—line,station
at accepts HH:MM (next occurrence — rolls to tomorrow if past) or
YYYY-MM-DD HH:MM (exact). Failures raise proper HA errors, visible in
automation traces.
- Polling: buses every 2 minutes, trains every minute — one request per station, not per entry. Any number of train journeys boarding at the same station share a single fetch, since which way you travel is applied afterwards. A there-and-back commute costs two requests a minute because the two legs genuinely depart from different places. The services below sit outside this and always fetch live, so a fast automation loop calling one adds requests of its own. Reloading an entry always refetches.
- On HTTP 429 the Status entity's
rate_limitedattribute goes true and polling backs off, doubling from the entry's own interval up to 15 minutes, resetting after a success. Transperth sends noRetry-Afterheader and its cooldown is sticky and shared with their public website, so polling straight through a rate limit only prolongs it — which is why every train entry at a station shares the one rejection rather than each asking again. - Data comes from Transperth's unofficial website APIs (there is no official realtime feed). It can break without notice; entities go unavailable and recover on their own.
- Every time the integration handles is
Australia/Perth-aware regardless of your server's timezone, so the departure-boardHH:MMstrings are always Perth local time — which is what you want, but will differ from the rest of your dashboard if Home Assistant is set to another zone.
Existing train entries keep working and become station entries, reporting both
directions. Their per-destination sensors are replaced by per-direction ones,
so sensor.…_next_departure and sensor.…_next_train_to_perth give way to
sensor.…_next_train_towards_perth — update any automations referencing them.
To get the journey behaviour, add a new entry and set Travelling to.
Delete the integration entries under Settings → Devices & Services, then remove the repository from HACS.
MIT