The aim of this project is to build a live map of the full vienna metro system displaying each trains approximate position using a Raspberry Pi. For this on each line the next departures of trains from specified stations is requested, interpolated for all stations, and stored. From this data the state of LEDs (one LED per station and direction) on each line is computed every second. The LEDs are powered via three 16-bit serially connected LED sink drivers and P-channel mosfet transistors, allowing for multiplexing each line and therefore minimizing the number of GPIO pins needed.
Important
The code uploaded here represents the current state of the project, it works but still has it's flaws and will get modifications in the future.
Warning
The program sends GET requests to the the API of WienerLinien. During normal operations these should not occur less than 'min_refresh_intervall' seconds apart (defaultvalue=60, setting it <30 is discouraged). However, changes to the code could potentially bypass this restriction, which could result in your IP address being blocked.
For the physical representation, I settled on the following specifications:
| Name | Function | Specifications | The part I chose |
|---|---|---|---|
| Frame | house all electronics | 70x50cm | from Ikea |
| Map | a map of vienna's metro | printed after slight modifications | downloadable here |
| Computer | run the program | GPIO, WLAN, able to process 36KB of json data at once | Raspberry Pi 5 |
| LED control | power LEDs | multiplexing due to restrictions on # of GPIO pins | 3xSTP16CP05MTR and 5 P-channel Mosfets |
My project was inspired by an instagram post made by 'designrulesco' from July 2024, link to the original post is here. Further info on their product webisite where they sell metro-styled maps of cities in the USA, where the stations illuminate whenever a train is departing from them.
My goal was to do just that, but here in Vienna :)
Fortunately WienerLinien, the company operating the Vienna metro provides a real-time API system, through which in particular one can request future departures for each station. No API-key required. Documentation and further informations can be found here: https://www.wienerlinien.at/ogd_realtime/doku/ . In the following I want to give a short introduction on how I used this service to create the 'WienerLinienMonitor'.
The metro system consists currently of 109 stations, with stations served by more than one line counted for each line. A geographical location of all metro stations generated by 'WienerLinien.ipynb'
Given the fair-use principle of the API (request as little data as possible) and statement that the interval between two requests should not be less than 15 seconds, this presents a challenge of how to continuously retrieve up-to-date data across the entire metro system. During numerous iterations, I followed different strategies, a few of which should be listed below.
- It suffices to only rely on data from a few stations (as equidistant spaced as possible) and interpolate the departure times.
- The choice of stations that are meassured should reflect the current service of the metro (e.g. if a line is not in service end-to-end, but is divided into two independently operating lines).
- If we could assign every station a value for the 'necessity' of update during runtime and dynamically depending on current data, we could only update those.
- Stations serving more than one line include more data per requested station, therefore appear to be more valuable.
- Only handling one line at a time reduces complexity and difficulty.
- Data only reflects where a train is headed to, so lines with more then one terminal station per direction have to be meassured past every intermediate terminal station in order to track the origin station of a train correctly.
Point 3 would be highly desirable if implemented efficiently and correctly, but I consider it to be too prone for 'bias' towards certain stations, giving insufficient data on the rest of the system. Combining Point 1, 4 and 6 was my first strategy, but complexity of handeling the data, in particular the incomplete picture one has in the case where only given departure data of one station for a single line at a time, made it susceptible for errors when matching older data to this new data. Hence for this version I settled with Point 5 additionally to the Points 1, 2 and 6. The current operating state of the metro is computed at the begining of the program. For this a set of stations is given by the value 'initial_meassure' in the file 'Config.ini'. These might need to be changed, if continuos service of any line is interrupted. The program requests departure data from all those stations at once, computing for each line the terminal stations most distant to the station meassured (these are called service intervals) as well as for each line a set of terminal stations (for both directions at a time). The departure data is then discarded, since it is not of use anymore and from the service intervals and terminal stations the to be meassured stations are then determined. A visualisation (printed to 'monitor.log') might look like this:
Note
Work in progress, significant parts are still missing here.
Roughly speaking, even numbers correspond to visualisations relying on if the train is currently in the station or not where odd numbers correspond to lighting the stations nearest to each train.
| display-mode | description |
|---|---|
| 0 - train in station | LED is turned on, if calculation shows a train is currently at this station |
| 1 - nearest station | For each train tracked, the station closest to it's position is lit up. On the trains terminal/origin station the LED is turned on at train_in_station_time/2 seconds before departure |
| 2 - train in station, blink on departure | like display-mode 0, but LED blinks once train is about to depart |
| 3 - nearest station, blink before transition | like display-mode 1, but before the nearest station of the train changes, the LED is blinking |
| 4 - train in station, blink on arrival | as display-mode 2, but in reverse time |
| 5 - nearest station, blink after transition | like display-mode 3, but reverse |
| 6 - train in station, blink on transition | imagine display-modes 2 and 4 put together |
| 7 - nearest station, blink on transition | similarly display-mode 3 and 5 combined |
| section | parameter | range | default | description |
|---|---|---|---|---|
| [DEFAULT] | ||||
flag_debug |
0 or 1 | 0 | If 1, then program will run in debug_mode, it will use data stored in /debugfiles instead of fetching data from API-requests |
|
debug_time |
list[int] storing time used when in debug_mode | |||
logging_level |
0 or 1 | 1 | Debug-messages show up in monitor.log iff logging_level=0 |
|
lines |
list[str] | all lines | only lines entered here are tracked | |
initial_meassure |
list[str] | stations initially meassured to calculate service intervals and terminal stations of trains | ||
| [FETCH] | ||||
min_refresh_intervall |
int, >=30 | 60 | no GET request is sent, if last is less than min_refresh_intervall seconds ago |
|
max_refresh_intervall |
int | 180 | GET request is sent, if the line not updated for the longest, has not been updated since at least max_refresh_intervall seconds |
|
meass_stations_per_line |
int | 6 | # of Stations for which a GET request is sent on each line | |
url_start |
str | starting sequence of url for GET request | ||
url_inbetween |
str | intermediary sequence in url between stations | ||
| [METRO] | ||||
train_dep_cutoff_time |
int | -100 | train will be discarded, if every departure of it lays back more than train_dep_cutoff_time seconds |
|
max_trains_on_line |
int | 20 | maximal # of trains tracked at one time on each line | |
threshold_time_between_departures |
int | 100 | seconds, time deviation permissible for departures meassured at different times to be considered to be the same train. If deviation is less then 2*threshold_time_between_departures and the best match, it will still be counted as a match but a warning is logged. |
|
display_mode |
int, 0-7 | 1 | see display modes | |
train_in_station_time |
int | 30 | seconds each train is assumed to halt at one station | |
train_departure_delay_time_offset |
int | 0 | seconds added to departuretime of each train. An experimental feature not yet tested | |
| [MONITOR] | ||||
flag_monitor_debug |
0 or 1 | 0 | If =1, then a window with a matplotlib representation of the data is shown instead of driving LEDs | |
debug_speed |
int, >=1 | 10 | Simulated time is multiplied with this factor | |
frame_rate |
int | 20 | # of times the program switches lighting all lines per second | |
duty_cycle |
float | 0.8 | fraction of time the LEDs of one line are powered on per reserved time for lighting one line | |
blink_half_period |
float | 1 | seconds the LED is either ON or OFF if in blinking mode | |
pin_displaymode |
int, GPIO compatible | 26 | pin # for external control of displaymode | |
pin_monitor_on |
int, GPIO compatible | 19 | pin controling turning on the monitor | |
pin_exit |
int, GPIO compatible | 13 | interupt pin shuting down program | |
pins_line_select |
list[int], GPIO compatible | output pin of line that is set to be displayed, is turned low | ||
pin_sdo |
int, GPIO compatible | 12 | serial data out pin | |
pin_clk |
int, GPIO compatible | 16 | serial clock pin | |
pin_oe_not |
int, GPIO compatible | 20 | pin disabling powering the LEDs | |
pin_le |
int, GPIO compatible | 21 | pin latch enable | |
shift_register_size |
int | 48 | size of shift register |