Helsinki Tram Time Display

The goal of this project was to avoid waiting at the tram-stop in sub-zero temperatures, which 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 a couple of minutes we could leave the house in safety, if there was a wait of 10+ minutes we'd stay indoors.


The hardware for this project is simple:

  • 1 x WeMos Mini D1
    • Approximately cost €2.50.
  • 1 x Push-Button
    • Approximate cost €0.05.
  • 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 merely consists of connecting the display to the Mini D1 board, which is in turn powered by a simple USB-PSU, then adding in the push-button as an optional extra. The display-wiring is as follows:

  1. Vcc -> Vcc (3.3v)
  2. Ground -> Ground
  3. SCL -> WeMos D1 D1
  4. SDA -> WeMos D1 D2

The push-button is connected between D0 & D8, and is entirely optional


The hardware

The initial wireup.

With a button

Adding a button between D0 & D8..

Fitting the items into the box

Fitting things into the box I had 3d-printed.

The result, boxed

The boxed result.


The software for this project is logically divided into a few distinct 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.
  • Handling button-input, if this is added.

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. The web GUI allows changing the time-zone, which is necessary if you're outside GMT.

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 current date & time, along with the upcoming departure times.

If you've never configured the device it will instead notice that, and begin to operate as a WiFi access-point - you can use your mobile phone, or other WiFi-connected device to connect to this access-point and choose the local WiFi network it should join.

This project doesn't require any recompilation to change the networking details, the tram-stop, or the timezone.

Once configured the system will:

  • Update the date/time display once a second.
  • Resync the time, via NTP, every five minutes to avoid drift.
  • Serve a simple GUI via HTTP
    • This mirrors the output, shown on the screen, as well as allowing changes to be made.
    • Control the backlight.
    • Change the tram-stop being monitored.
    • Change the TimeZone offset (i.e. This project is for Helsinki, so we're at GMT + 2.)

The button, if present, allows two things :

  • A short-press
    • Toggles the state of the backlight.
  • A long-press
    • Triggers an immediate resync of the date & time, as well as the tram-data..

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 addition of the access-point functionality. You no longer need to recompile the code to configure your WiFi details, change the tram-stop, or adjust the timezone offset.

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 code has also recently been updated to support over-the-air updates, directly from the arduino IDE.

Finally the built-in HTTP-server shows the tram-data, along with explicit links for controlling the backlight, changing the tram-stop, and changing the timezone.