Helsinki Tram Time Display

The goal of this project was to avoid waiting at the tram-stop in sub-zero temperatures. This would be achieved by having a constantly-updating display showing the next departure time from our local tram-stop.

If there is a tram due in <3 minutes we could leave the house in safety, if there was a wait of 10+ minutes we'd stay indoors, enjoying our deliciously insulated flat.


The hardware for this project is simple:

  • 1 x WeMos Mini D1
    • Approximately cost €2.50.
  • 1 x I2C LCD Display
    • Approximate cost €1.50.
    • We've previously documented using an I2C LCD with an Arduino.
    • In my project the display was initially 2x16, but the code allows for different dimensions.
      • I soon upgraded it to a 4x20 display instead, as pictured below.

Wiring the merely consists of connecting the display to the Mini D1 board, which is in turn powered by a simple USB-PSU:

  1. Vcc -> Vcc
  2. Group -> Ground
  3. SCL -> WeMos D1 D1
  4. SDA -> WeMos D1 D2


The result..

The result, boxed


The software for this project is logically divided into four parts:

  • Handling connections to the local WiFi network.
    • You need an IP so you can connect to the internet to fetch the timing details.
  • Fetching the current date & time, via NTP.
    • Drawing that date & time.
  • Fetching the tram-time for the local tram-stop.
    • I had to introduce a simple tram-API to cut down on the processing on the device itself.
    • Drawing that data.
  • Allowing over the air updates, directly from the arduino IDE.

For dealing with the date & time, including retrieving the current time via NTP, I used the standard time library, which has excellent support for such things.

Retriving the departure-times for a given tram-stop is pretty straight-forward, but unfortunately I implemented my project at a time when the Helsinki transport system switched their public API away from being a simple HTTP-fetch of JSON data, now it is a little more verbose.

To simplify the complexity of dealing with that new API, which in fact is pretty flexible and straight-forward, I setup a simple proxy to return the appropriate departure-details as CSV via the numerical stop-ID:

Parsing CSV is much simpler for the hardware, which is barely able to make HTTPS calls, but I will say the Helsinki documentation was excellent. I managed to make this transition in only a couple of hours.


If you've previously configured WiFi details the device will connect automatically, and begin showing the date/time, along with the departure times.

If you've never configured the device it will instead notice that, and configure itself as an access-point. This means you can use your mobile phone, or other WiFi-connected device, to connect to this access-point and choose the local access-point it should become a member of.

In short this project doesn't require any recompilation to change the networking details, instead you can operate it in your local environment with ease.

Once configured the system will:

  • Update the date/time once a second.
  • Resync the time, via NTP, every five minutes to avoid drift.
  • Serve a simple web page via HTTP
    • This lets you see the output, control the backlight, and change the tram-stop being viewed.

The Code

The main code looks like this:

Several libraries are used, and these are bundled together in a git repository, you can access all the source by cloning the repo involved, and then looking at the project:

Recent Changes

The project has become much more user-friendly thanks to the access-point functionality - you no longer need to recompile the code to configure your WiFi details.

Previously the display was hard-wired for 2x16 LCDs, now this is flexible via #define statements - I used this facility to update the display from 2 rows of 16-characters (2x16) to 4 rows of 20 characters (4x20):

#define NUM_ROWS 4
#define NUM_COLS 20

The parsing of tram-times now shows the correct route-number, from the remote JSON response, rather than always showing "Tram 10" (which is the only line near me). This means if you update the stop-ID it will show "Tram 3", "Tram 7A", etc.

Now the built-in HTTP-server shows the tram-data, along with explicit links for controlling the backlight and changing the tram-stop, and the code has also been updated to support over-the-air updates, directly from the arduino IDE.

Finally we also cope with BST changes, automatically.