June 2, 2019

Kitchen Clock with Public Transport Schedule

I have created a Kitchen Clock with a build in e-ink display that shows the departure times of the next tram station.

Kitchen Clock with Public Transport Schedule

For my birthday I was gifted an Arduino starter set. This present called for a maker project. For this I have created a kitchen clock with a build in e-ink display that shows the departure times of the next tram station. This allowed me to reduce the waiting times at the tram stop in the morning. This project, which included the following technologies

I want to showcase in this post.

Update July 2019: I switched to a Raspberry Pi Zero for the Central device. This is described in this post: ble-central-device-with-node-js/

Basic Idea

The basic idea is to recreate the digital sign at the tram stops inside a regular kitchen clock.

This information should be available at the glance at the kitchen clock.

I started out with a Wemos Lolin D32 development board that had wifi and bluetooth onboard. My first task was to use the board to get the departure times from the local transport company (MVG) and extract the relevant information.

After a suggestion from a colleague I setup the Lolin32 board to broadcast the departure information via Bluetooth low energy (BLE) to a second micro controller board. This is the Adafruit Feather nRF52 with BLE onboard. The Lolin32 pushes the departure information over BLE as soon as the information changes. This is important for the Feather, since the Feather as well as the e-Paper should consume as little energy as possible. With this push strategy it is assured that the Feather only wakes if the display needs updating. Feather and e-Paper are running on battery power.

Next I set up the e-Paper to display these information. The e-Paper is hooked up to the Feather. Setting up the e-Paper included some noteworthy tweaks. Namely I tuned the display to a fast refresh mode, which resolved the annoying flickering of the e-Paper default page refresh. And secondly I had to created a custom font to display the letters on the display in an appropriate size.

I included the e-Paper display into a round kitchen clock. This was maybe the easiest part, since the back of the clock is made from plastic.

The ready made clock with departures from our home tram and bus stop.

Lolin32 and Public Transport

The first task was to connect the Lolin32 board to my local wifi network. As a programming environment I have used the offline ArduinoIDE. Connecting the Lolin32 to the network was quite easy using the samples provided with the ArduinoIDE, e.g. "WiFiClientSecure".

Next I used the API development tool Postman to play with the API of the local public transport company MVG. Unfortunatly I could not find an official documentation of the transport schedule API of the MVG, but I found the Github project mvg-api that was using it. From this project I could understand how to put together a http request for fetching the departure times I was interested in. Using the Postman tool to build the http request is a lot quicker compared to testing it on the Lolin32. After I constructed the query that returned the right data, i.e. departure information for the station that I am interested in, I inserted the request into the Arduino code. The http request looked something like this:

GET https://www.mvg.de/fahrinfo/api/departure/1109?footway=0 HTTP/1.1
Host: www.mvg.de
X-MVG-Authorization-Key: 5af1beca494712ed38d313714d4caff6
Connection: close
Http request for fetching departure times for station with id 1109 from MVG.

Converting from Unix Time to Date

The Lolin32 does not have a build in clock. And since I want to extract the duration in minutes for the departures I had to get the server time from the server response header and compare it to the Unix Time of the departures. For this I found a bit of code on the web that does the conversion from the date string in the server response to a DateTime type:

boolean getDateFromHeader()
{
  char buf[5];
  if (client.find("Date: ") && client.readBytes(buf, 5) == 5)
  {
    int day = client.parseInt();    // day
    client.readBytes(buf, 1);       // discard
    client.readBytes(buf, 3);       // month
    int year = client.parseInt();   // year
    int hour = client.parseInt();   // hour
    int minute = client.parseInt(); // minute
    int second = client.parseInt(); // second
    int month;
    switch (buf[0])
    {
    case 'F':
      month = 2;
      break; // Feb
    case 'S':
      month = 9;
      break; // Sep
    case 'O':
      month = 10;
      break; // Oct
    case 'N':
      month = 11;
      break; // Nov
    case 'D':
      month = 12;
      break; // Dec
    default:
      if (buf[0] == 'J' && buf[1] == 'a')
      {
        month = 1; // Jan
      }
      else if (buf[0] == 'A' && buf[1] == 'p')
      {
        month = 4; // Apr
      }
      else
        switch (buf[2])
        {
        case 'r':
          month = 3;
          break; // Mar
        case 'y':
          month = 5;
          break; // May
        case 'n':
          month = 6;
          break; // Jun
        case 'l':
          month = 7;
          break; // Jul
        default: // add a default label here to avoid compiler warning
        case 'g':
          month = 8;
          break; // Aug
        }
    }
    serverTime = DateTime(year, month, day, hour, minute, second);
    return true;
  }
  return false;
}
Conversion of time header in server response to DateTime.

Bluetooth Low Energy Setup

The configuration of Bluetooth low energy (BLE) in this setup is as follows. The Lolin32 will be plugged into a power suply and call the MVG service on regular time intervals. The logic on the Lolin32 will determine if the minutes till a departure have changed and only then push the updated data to the Feather. In this setup the Lolin32 serves as a server and the Feather serves as a client.

The Lolin32 is acting as a BLE central device. This means that all connections are established by it. The Feather will act as a peripheral device and request a connection to the central. An entry point for more details on the BLE architecture can be found e.g. at the Nordic Forum.

Fast Refresh Mod for e-Paper

For showing the data on the clock I wanted a low energy display that could run for several months only on battery power. For this reason I decided on the e-Paper display from Waveshare.

The default refresh mode of the e-Paper, i.e. when a new frame is to be shown on the display, is turning all pixels white, then black, then white again. Only after this cycle the actual frame is displayed on the device. After digging around on the web I found that the hacker Ben Krasnow dug around the acual workings of the device and presents a driver mod on his blog. What a joy to have access to such niche information – hurray the internet!

After playing around with the information from Ben Krasnow I actually got the device to fast refresh.

Fast Refresh Mod for Waveshare e-Paper Display.

Creating Custom Font for e-Paper

The e-Paper display comes with a limited set of font sizes. Unfortunatly I needed a bigger font to make the departure times clearly visible from across the room. This was a challenge, that again was solved by the infinite diversity of the www.

I found a tool with which it is possible to create custom fonts in the format needed by the e-Paper driver, i.e. a cpp header file. This tool is called lcd-image-converter. The program runs on Windows or can be compiled from source.

Data Send from Lolin32 to Feather

The data is pushed from the central Lolin32 to the Feather. The data is transported in a so called BLE characteristic. Unfortunatly the BLE spectification only allows 20 Bytes for a characteristic. I seems that in principle this could be extended to up to 512 Bytes, but I did not get it to work with more that 20 Bytes for the combination of Lolin32 and Feather. So I encoded the information I want to send into 20 Bytes like this:

uint8_t payload[20];

payload[0] = line1[0];
payload[1] = line1[1];
payload[2] = line1[2];
payload[3] = stationId1;
payload[4] = stationId1 >> 8;
payload[5] = departureTime1;
payload[6] = departureTime1 >> 8;
payload[7] = departureTime1 >> 16;
payload[8] = departureTime1 >> 24;
payload[9] = timeTillDeparture1;
payload[10] = line2[0];
payload[11] = line2[1];
payload[12] = line2[2];
payload[13] = stationId2;
payload[14] = stationId2 >> 8;
payload[15] = departureTime2;
payload[16] = departureTime2 >> 8;
payload[17] = departureTime2 >> 16;
payload[18] = departureTime2 >> 24;
payload[19] = timeTillDeparture2;
Encoding two lines to display into 20 Bytes with station id and departure time in minutes.

Of course this needs to be decoded on the Feather:

struct Connection
{
  char line[4];
  uint16_t destinationId;
  uint32_t departureTime;
  uint8_t minutesToDeparture;
};
struct Connection connections[2];

...

connections[0].line[0] = line1[0] = payload[0];
connections[0].line[1] = payload[1];
connections[0].line[2] = payload[2];
connections[0].line[3] = '\0';
connections[0].destinationId = (payload[4] << 8) | payload[3];
connections[0].departureTime = (payload[8] << 24) | (payload[7] << 16) | (payload[6] << 8) | payload[5];
connections[0].minutesToDeparture = payload[9];
connections[1].line[0] = payload[10];
connections[1].line[1] = payload[11];
connections[1].line[2] = payload[12];
connections[1].line[3] = '\0';
connections[1].destinationId = (payload[14] << 8) | payload[13];
connections[1].departureTime = (payload[18] << 24) | (payload[17] << 16) | (payload[16] << 8) | payload[15];
connections[1].minutesToDeparture = payload[19];
Decoding the data to display on the Feather.

Modifying the Wall Clock

I bought a minimalistic used wall clock off ebay to modify with the e-Paper. It was a pretty easy task since the back of the clock was made from plasic. It just took a demel rotary tool and some patience:

Power Consumption

I connected the e-Paper display and a lipo battery to the Feather. After fully charging the battery and connecting the BLE link of Feather and Lolin32 everything was running smoothly. Unfortunatly the battery only lasted for a few days. So I dug a bit into optimizing the power consumption of the Feather. To check the improvements of any tuning I measured the current draw of the Feather with a multimeter.

It turned out that there is a simple way of drastically reducing the power consumption. I read a bunch of web posts which I do not recall, but it boils down to registering a callback function on the characteristic update event. The callback function takes the updated characteristic, decodes it and writes the departure times to the display. This event triggered operation ensures that the device only wakes if data needs to be processed and sleeps otherwise. This reduced the power consumption drastically and the battery should hold for a few months.

Source Code

I have pushed the source code the my Github repo. Check it out here.

I know that this post is far from a complete or perfect, thus feel free to contact me about any questions you have – happy hacking!