🚆 Transit Display for TTGO T-Display ESP32
A real-time departure board for your front door.
Powered by a TTGO T-Display ESP32 with a 240×135 color LCD — 33 firmware variants covering trains, metro, suburban rail, trams, buses, and ferries across Germany, Austria, Switzerland, the Netherlands, Belgium, Luxembourg, the UK, Denmark, Sweden, Norway, Finland, Estonia, Poland, Czechia, Hungary, Greece, and Portugal — 17 European countries, 17 capitals, over 150 cities.

Grab your jacket at the right moment instead of refreshing an app.
Plug it in, point it at your local public transport stop, done.
Contents
✨ Features
Display
- 240×135 color TFT with line-correct badge colors, destination, platform, and minutes-until-departure
- Countdown turns red when a vehicle is leaving in under 2 minutes
- Idle clock mode — after 5 minutes with no input, the screen switches to a large HH:MM + date view; any button press brings the departures back
Live data
- Pulls real-time departures every 30 seconds from official or community transit APIs
- Re-renders every second so the countdown stays accurate
- Line & destination filtering — show only the lines you care about, hide directions you don’t
Configuration
- One config file per project — no source changes needed to retarget a stop
- Multi-stop carousel — define several stops and cycle through them with a button
- First-boot Wi-Fi setup — enter your password once over the Serial Monitor; it’s stored in NVS so credentials never sit in your source tree
🗺️ Supported networks
Thirty-three firmware variants, one per network. Pick the one for your city and flash it.
Germany
| Project |
Network |
Coverage |
DB_ESP_LCD |
Deutsche Bahn (nationwide) |
All DB stations — Köln, Frankfurt, Stuttgart, Düsseldorf, Leipzig, Nürnberg, Dortmund, Essen, Bonn, Münster, Augsburg, Wiesbaden, Aachen, Freiburg, Mainz, Ulm, and many more |
TRIAS_BW_ESP_LCD |
Baden-Württemberg (NVBW EFA) |
Stuttgart, Karlsruhe, Mannheim, Heidelberg, Freiburg, Ulm, Tübingen, Reutlingen, Heilbronn, Pforzheim, Friedrichshafen + lokale S-/Stadtbahn/Tram/Bus. Kein Key. |
TRIAS_BY_ESP_LCD |
Bayern (MVV EFA) |
Augsburg, Nürnberg, Würzburg, Regensburg, Ingolstadt, Kempten, Erlangen, Bayreuth, Passau, Landshut, Garmisch + München (via Stop-ID). 12 Städte verifiziert. Kein Key. |
BVG_ESP_LCD |
Berlin (BVG) |
Berlin — U-Bahn, S-Bahn, Tram, Bus, Ferry |
VBN_ESP_LCD |
Bremen / Hannover (VBN) |
Bremen, Hannover, Bremerhaven, Oldenburg |
DVB_ESP_LCD |
Dresden (DVB / VVO) |
Dresden + Oberelbe — Tram, Bus, S-Bahn, regional trains |
HVV_ESP_LCD |
Hamburg (HVV) |
Hamburg — U-Bahn, S-Bahn, Bus, Ferry |
VMV_ESP_LCD |
Mecklenburg-Vorpommern (VMV) |
Rostock, Schwerin, Stralsund, Greifswald |
MVV_ESP_LCD |
München (MVV / MVG) |
Munich + surrounding area |
TRIAS_ESP_LCD |
NRW — Universal (EFA / VRR OpenService test endpoint) |
NRW, Köln/Bonn, Aachen + große deutsche Bahnhöfe via DELFI-Mesh (Hannover, Hamburg, Berlin, Bremen, Dresden, Kassel). 24 Städte live verifiziert. Kein Key. |
VRN_ESP_LCD |
Rhein-Neckar (VRN) |
Mannheim, Heidelberg, Ludwigshafen, Kaiserslautern |
INSA_ESP_LCD |
Sachsen-Anhalt (INSA) |
Halle, Magdeburg, Dessau, Wittenberg |
NAH_ESP_LCD |
Schleswig-Holstein (NAH.SH) |
Kiel, Lübeck, Flensburg, Neumünster |
VSN_ESP_LCD |
Süd-Niedersachsen (VSN) |
Göttingen, Hildesheim, Goslar, Hameln |
VMT_ESP_LCD |
Thüringen (VMT) |
Erfurt, Jena, Weimar, Gera |
Switzerland
| Project |
Network |
Coverage |
CH_ESP_LCD |
SBB (nationwide + local) |
Zürich, Bern, Basel, Genève, Lausanne, Luzern, Winterthur, St. Gallen — trains, trams, buses, ferries, cable cars |
ZVV_ESP_LCD |
Zürich (ZVV) |
Zürich region — S-Bahn, Tram, Bus |
Austria
| Project |
Network |
Coverage |
OeBB_ESP_LCD |
ÖBB (nationwide) |
Wien, Salzburg, Graz, Innsbruck, Linz, Klagenfurt — RailJet, REX, S-Bahn |
WL_ESP_LCD |
Wien (Wiener Linien) |
Vienna — U-Bahn, Tram, Bus |
Belgium
| Project |
Network |
Coverage |
STIB_ESP_LCD |
Brussels (STIB / MIVB) |
Brussels — Metro, Tram, Bus |
Czechia
| Project |
Network |
Coverage |
GOLEMIO_ESP_LCD |
Prague (PID via Golemio) |
Prague Integrated Transport — Metro A/B/C, Tram, Bus, Trolleybus, Esko rail, Vltava ferry, Petřín funicular. Free key. |
Denmark
| Project |
Network |
Coverage |
DSB_ESP_LCD |
DSB / Rejseplanen |
Copenhagen, Aarhus, Odense — S-Tog, Metro, Letbane, IC, Regional |
Estonia
| Project |
Network |
Coverage |
PEATUS_ESP_LCD |
peatus.ee (Digitransit Finland endpoint) |
Tallinn, Tartu, Pärnu, Narva, Viljandi + nationwide — Tram, Trolleybus, Bus, Elron rail, Ferry. GraphQL, same Digitransit key as Helsinki. |
Finland
| Project |
Network |
Coverage |
DIGITRANSIT_ESP_LCD |
Helsinki (HSL Digitransit) |
Helsinki, Espoo, Vantaa, Kauniainen + 5 more municipalities — Metro, Tram, Bus, Train, Ferry. GraphQL, free key. |
Greece
| Project |
Network |
Coverage |
OASA_ESP_LCD |
Athens (OASA Telematics) |
Athens — Metro, Tram, Trolleybus, Bus. No key. |
Hungary
| Project |
Network |
Coverage |
BKK_ESP_LCD |
Budapest (BKK Futár) |
Budapest urban transit — Metros M1/M2/M3/M4, Trams, Buses, Trolleybuses 70-83, HÉV rail H5-H9, BKK Boats, Budavári Sikló. Free key. |
Luxembourg
| Project |
Network |
Coverage |
MOB_LU_ESP_LCD |
mobiliteit.lu |
Nationwide — CFL trains, Lux City tram, RGTR/AVL buses, Pfaffenthal-Kirchberg funicular. HAFAS REST, free key by email. |
Netherlands
| Project |
Network |
Coverage |
OVAPI_ESP_LCD |
OVapi (nationwide) |
Amsterdam, Rotterdam, Den Haag, Utrecht + nationwide — Metro, Tram, Bus, Ferry. No key. |
Norway
| Project |
Network |
Coverage |
ENTUR_ESP_LCD |
Entur (nationwide) |
Oslo, Bergen, Trondheim, Stavanger + all NSR stops — train, bus, tram, metro, ferry. GraphQL, no key. |
Poland
| Project |
Network |
Coverage |
ZTM_ESP_LCD |
Warsaw (ZTM) |
Warsaw — Metro, Tram, Bus |
Portugal
| Project |
Network |
Coverage |
CRM_ESP_LCD |
Lisbon (Carris Metropolitana) |
Lisbon metropolitan area — regional bus network. No key. |
Sweden
| Project |
Network |
Coverage |
TRAFIKLAB_ESP_LCD |
Stockholm (SL via Trafiklab) |
Greater Stockholm — Tunnelbana, Pendeltåg, Bus, Tram, Ship. Free key; nationwide-swap documented. |
United Kingdom
| Project |
Network |
Coverage |
TFL_ESP_LCD |
London (TfL) |
All Tube, Overground, DLR, Elizabeth line, Tram, Bus |
Your country/city missing? Open an issue. Most networks publish a free real-time API and adapting an existing sketch is straightforward.
🎯 Cities wanted — top 10 contributions we’d love next
| Rank |
City |
Operator / API |
Likely effort |
Clone-from |
| 1 |
Madrid |
EMT MobilityLabs (free key, OAuth-ish) |
🟡 medium — custom auth flow |
TRAFIKLAB_ESP_LCD |
| 2 |
Barcelona |
TMB Open Data (free key, REST/JSON) |
🟢 low — clean REST |
GOLEMIO_ESP_LCD |
| 3 |
Dublin |
NTA / TFI REST (free key) |
🟢 low |
TRAFIKLAB_ESP_LCD |
| 4 |
Milan |
ATM proprietary REST |
🟡 medium — undocumented schema |
TFL_ESP_LCD |
| 5 |
Rome |
Roma Servizi / Atac (mixed) |
🟡 medium — fragmented operators |
TFL_ESP_LCD |
| 6 |
Paris |
PRIM SIRI Lite (free key, XML) |
🔴 high — SIRI XML is heavy |
new template |
| 7 |
Lyon |
TCL Apicil REST (free key) |
🟡 medium |
TRAFIKLAB_ESP_LCD |
| 8 |
Bratislava |
IDS BK OpenData (free key) |
🟢 low |
GOLEMIO_ESP_LCD |
| 9 |
Reykjavik |
Strætó (key by application) |
🟡 medium — slow approval |
MOB_LU_ESP_LCD |
| 10 |
Zagreb |
ZET (currently GTFS-RT only — needs a slim JSON proxy) |
🔴 high — would need a Cloudflare Worker |
new template |
Pick a city, clone the suggested sketch as a starting point, swap the URL + parser, and open a PR. The existing 33 sketches form a recipe book — each major API family (HAFAS, EFA rapidJSON, OneBusAway/OTP, Digitransit GraphQL, Trafiklab v5, proprietary REST) has at least one fully-debugged example to crib from.
🛠️ How it works
Each project is a standalone Arduino sketch for the LilyGo TTGO T-Display — an ESP32 with a built-in 240×135 IPS color TFT and two buttons.
Boot flow
- Connect to a known Wi-Fi network (SSID list from your config, password from NVS).
- Sync time over NTP.
- Fetch the next ~20 departures every 30 seconds from the transit API.
- Filter by line and destination per your config, sort by departure time, display the top 8.
- Re-render every second so the minute countdown stays accurate.
On-device controls
| Button |
Action |
| Top (GPIO 35) |
Put the display to sleep / wake it up |
| Bottom (GPIO 0) |
Cycle to the next configured stop |
Idle clock: if no button is pressed for 5 minutes, the departure list is replaced by a large clock view. The next press of either button consumes itself to wake the departures back — you can’t accidentally trigger sleep or cycle stops with the wake-up press.
⚡ Quick start
- Arduino IDE 2.x
- ESP32 board package — Boards Manager → search “esp32” by Espressif
- Libraries (Library Manager):
TFT_eSPI, ArduinoJson, Button2
- Configure
TFT_eSPI for the T-Display by uncommenting #include <User_Setups/Setup25_TTGO_T_Display.h> in the library’s User_Setup_Select.h.
2. Clone the repo
git clone https://github.com/johannesmutter95/ttgo-departure-board.git
3. Open the project for your city
Each project lives directly under the repo root as <NETWORK>_ESP_LCD/ (e.g. BVG_ESP_LCD/, MVV_ESP_LCD/, TFL_ESP_LCD/). Open the .ino file in Arduino IDE — the sketch folder already matches the .ino filename, as Arduino requires.
4. Set up the config
Each project ships with a config_<network>.h.dist template (e.g. config_bvg.h.dist, config_mvv.h.dist, config_tfl.h.dist). Copy it to drop the .dist suffix:
cp config_bvg.h.dist config_bvg.h # substitute your network
Edit the configs[] array: Wi-Fi SSID, stop ID, label, and which lines/destinations to include or exclude. Your Wi-Fi password is not in the config — you’ll enter it once on first boot.
Each project’s own README has copy-paste-ready stop IDs for major stations and a curl one-liner for searching the API by name.
5. Flash & first boot
- Select board ESP32 Dev Module (or TTGO T-Display if installed) and upload.
- Open the Serial Monitor at 115200 baud.
- On first boot the device shows “WiFi Setup” — type your Wi-Fi password and press Enter.
- The password is stored in NVS (non-volatile storage) and survives reboots. You won’t be asked again unless the connection fails.
Once the time syncs over NTP, departures appear. ✅
🛒 Buy the parts
Everything for the recommended build:
🇪🇺 EU + 🇨🇭 Switzerland
⚠️ Swiss customers: make sure the adapter is a slim Europlug (Type C), not a fat Schuko — Schuko’s side earth pins don’t fit Swiss Type J sockets.
United Kingdom
ℹ️ AliExpress and Amazon links are affiliate links. Same price for you, small commission for us — thank you for keeping the project going!
🖨️ 3D-printed case
Plenty of community-designed enclosures fit the TTGO T-Display. A few good starting points:
Pick whichever style fits your wall/desk — all of them mount around the same 51 × 26 mm board.
custom cities
| New city | Your local Network is not yet supported but has a public API? → open an issue or get in touch |
☕️ Support the project
If this saved you from missing a train or prevented extended waiting time at the station , consider:
🤝 Contributing
Pull requests welcome — adding a new city is usually a focused afternoon. Three docs you’ll want open:
- CONTRIBUTING.md — anatomy of a sketch + the 12-step “add your city” walkthrough
- docs/api-patterns.md — deep dive into the 8 transit-API families (HAFAS, EFA, OneBusAway/OTP, Digitransit GraphQL, Entur GraphQL, Trafiklab v5, proprietary REST, HMAC-signed)
- Good first issues — curated starter tasks (Dublin, Barcelona, Bratislava, small bug fixes)
🐛 Lessons we learned the hard way (short version)
- Trailing slashes matter — OVapi
/departures/ vs /departures is the difference between data and 404
- Times come in three flavours — unix int, ISO with offset, ISO without offset (= local TZ; set TZ before
mktime)
- Unsigned-int underflow in the refresh countdown shows up as
Akt. in 4294967280s and never resets
- ArduinoJson v6 filter shape mismatch (array vs object) silently drops all parsed entries
- Port 80 vs 443 geo-blocks + silent schema version drift (CRM v1→v2, Trafiklab V4→v5, BKK is OTP not OBA) bite when you least expect it
Full bug stories with code → CONTRIBUTING.md → Lessons we learned the hard way.
📄 License
GPL-3.0 — see LICENSE.
Based on original work by Florian Lederer, updated and extended by Johannes Mutter.
评论