In this post, I want to showcase a project I did over a recent weekend: a display for your home, that shows the Munich public transport departures in real time.
I mounted it next to the door in my apartment. This way I always know whether I’ll be able to catch my train or bus before leaving the house.
Naturally, it is aware of delays and cancellations.
The data is fetched from the API directly available from MVG, which is also used by the MVGO app as well as the website mvg.de. Internet connection is achieved over WI-FI.
Hardware Link to heading
The project is using a d1 mini microcontroller (ESP8266) and a 2.9" epaper display from WeAct Studio, both of which can be obtained on AliExpress for less than 15€ in total.
The epaper display is available in a black and white as well as a 3 colour (black, white and red) variant. I strongly recommend the black and white version, as the 3 colour version does not support partial refresh and has an overly long refresh time of 19s, during which the display flashes continuously.
Image 2: The d1 mini mounted to the back of the epaper display
The d1 mini is held in place by double-sided adhesive on the back of the display, and the included cables are directly soldered to the microcontroller. The wiring is as follows:
Display pin | wire color | d1 mini pin |
---|---|---|
1 (BUSY) | purple | 16 |
2 (RES) | orange | 5 (SCL) |
3 (D/C) | white | 4 (SDA) |
4 (CS) | blue | 15 (SS) |
5 (SCL) | green | 14 (SCK) |
6 (SDA) | yellow | 13 (MOSI) |
7 (GND) | black | GND |
8 (VCC) | red | 3V3 |
Image 3: Display wiring
The device can then be powered using a regular USB phone charger. The mounting holes can be used to mount it to the wall.
Software Link to heading
The firmware I wrote for the display is open sourced under the GPL and available on GitLab: https://gitlab.com/aahuber/esp-mvg-display It is build using platformio and the Arduino framework.
In order to build and flash it to the microcontroller, at first the config file src/config.h
should be changed. The following parameters are available:
Parameter | Description |
---|---|
WIFI_SSID | The SSID of the wifi network, to be connected to |
WIFI_PASS | The wifi password |
STATION_NAME | The name of the station (displayed in the title line on the display) |
GLOBAL_ID | The global ID of the station for which departures should be displayed. In order to obtain this for your station refer to the MVG API section |
SHOW_TITLE | If this is defined, a title line with the station name will be show |
OFFSET_MINUTES | Don’t show departures sooner than configured value |
Then, the firmware can be built and flashed to the ESP using:
pio run -t upload
If everything went correctly, the microcontroller should connect to the configured WI-FI network on boot and display the current departures on the screen.
MVG API Link to heading
To obtain current departure times the JSON API provided by MVG is used. While it is not publicly documented it is used by the MVG website and the MVG app, by using the browser’s development tools on the MVG website it is fairly easy to figure out how it works on a superficial level.
The API is provided under the endpoint https://www.mvg.de/api/fib/v2
.
To get the next 10 departures from Marienplatz, one can query the following URL: https://www.mvg.de/api/fib/v2/departure?globalId=de:09162:2&limit=10&transportTypes=UBAHN,TRAM,BUS,REGIONAL_BUS,SBAHN,SCHIFF
. (Here de:09162:2 corresponds to the global ID of the Marienplatz station)
The response will be a list of json objects looking something like this:
{
"plannedDepartureTime":1726232520000,
"realtime":true,
"delayInMinutes":6,
"realtimeDepartureTime":1726232928000,
"transportType":"UBAHN",
"label":"U6",
"divaId":"010U6",
"network":"swm",
"trainType":"",
"destination":"Klinikum Großhadern",
"cancelled":false,
"sev":false,
"platform":2,
"platformChanged":false,
"messages":[],
"bannerHash":"",
"occupancy":"MEDIUM",
"stopPointGlobalId":"de:09162:2:52:52"
}
The global ID of a station can be obtained via the API as well. For example, a query to https://www.mvg.de/api/fib/v2/location?query=Marienplatz&locationTypes=STATION
will return a list of stations containing Marienplatz in their name. (Note that on Marienplatz in Munich there are actually multiple stations and also other towns within the MVV area have a Marienplatz, hence multiple results).
Each station object in the response contains its global ID and looks something like this:
{
"type":"STATION",
"latitude":48.137245,
"longitude":11.575421,
"place":"München",
"name":"Marienplatz",
"globalId":"de:09162:2",
"divaId":2,
"hasZoomData":true,
"transportTypes":["UBAHN","BUS","SBAHN"],
"surroundingPlanLink":"MP",
"aliases":"Rathaus Bf. Bahnhof München Muenchen Munchen Alter Peter Peterskirche Frauenkirche Dom Liebfrauendom Meisterfeier Viktualienmarkt Kaufingerstrasse MP",
"tariffZones":"m"
}
Future Work Link to heading
There is a couple of things I’d like to add soon: - Error handling for failed requests to the API - Proper support of daylight saving time - 3D printed case and wall-mount