Building an E Ink weather display

About a year and a half ago I got my hands on an E Ink display built by a company called Waveshare. The original plan I had for that was to build a low-power photo frame that would display cute messages for my girlfriend every day for a year or so. After rendering about 400 pictures with cute, lovely messages and showing them for a year, the frame had reached its projected end-of-life and was subsequently parked on a shelf. However, a few months after that I decided to re-purpose it, both because I don’t like having hardware that sits and gathers dust, and also because my girlfriend mentioned at least a couple times that she’d like to see today’s forecast or some other useful details on it. So, I embarked on an adventure, built the thing, and now I’ll share with you how I did it, while also providing the source code and everything else that you’d need in order to make your own. So, without further ado, let’s go.

First of all, my version of the code together with the .svg files for the laser-cut frame can be found on my GitHub at github.com/GlitchOne/WeatherBot. But let’s explain what I did and how I did it, shall we?


The hardware:

  • Raspberry Pi 3 B+. You can probably use the RasPi 4, too, but this is what I had on hand.
  • 16Gb microSD card. You can use a bigger one, too.
  • 7 inch Waveshare E Ink display with driver board. While the RasPi is the brains of the project, this is the face. I had the v1 with the smaller pixel count.
  • A micro USB cable, to power the thing.
  • A USB power supply of any kind. This will work off of your PCs USB without breaking a sweat.
  • Some laser cut or 3D printed panels, to mount everything together.
  • 4 M2 screws at least 15MM in length, 4 M2 screws at least 12MM in length, 12 M2 standoffs. I had mine laying around.

The software:


Before we start writing the actual code that does the stuff with the showing of the things, we need to include some modules that will help us later. To do that, we first download the E Ink driver scripts from Waveshare for the appropriate screen that we have. I used the epd7in5.py and epdconfig.py scripts. We put those in the folder where we have the main script. After that we download a font and some icons that we will use to represent the weather and place them together with the main script.

Then, we start and including things in the main script:

import json
import os
import time
import epd7in5
import requests
from cairosvg import svg2png
from PIL import Image,ImageDraw,ImageFont

The first thing I needed to figure out after putting everything together was where should I get my weather data from. After about 10 minutes of Googling I settled on OpenWeatherMap, as they had a pretty cool, well documented API whose free version allows you to both get the current weather as well as the three hour weather forecast for the next five days which was more than enough.

Before getting the weather, we need to have two things. Our location code, and our weather API key. You can get the location code for your city from here and your API key should come by e-mail after you sign-up for the OpenWeatherMap service.

What you need to do is simply send a POST request to the weather or forecast API, and it will return a JSON object with all of the data you need.

url = "https://api.openweathermap.org/data/2.5/weather?id=YOURCITYID&appid=YOURAPIKEY&units=metric"
response = requests.post(url)

Running the code above makes a request to the weather API which will hopefully respond with a JSON that looks like this:

{
"coord":{"lon":-0.13,"lat":51.51},
"weather":[{"id":300,
            "main":"Drizzle",
            "description":"light intensity drizzle",
            "icon":"09d"}],
"base":"stations",
"main":{"temp":17.89,
        "pressure":1012,
        "humidity":81,
        "temp_min":18.01,
        "temp_max":20.12},
        "visibility":10000,
        "wind":{"speed":4.1,
                "deg":80},
"clouds":{"all":90},
"dt":1485789600,
"sys":{"type":1,
       "id":5091,
       "message":0.0103,
       "country":"GB",
       "sunrise":1485762037,
       "sunset":1485794875},
"id":2643743,
"name":"London",
"cod":200
}

In order to get the data that we need, we can treat that JSON file as a nested dictionary. Therefore, if we want to get the current temperature value, held under the “temp” key, we need to access

response.json()["main"]["temp"]

For more information about the other parameters, you can check the OpenWeatherMap API documentation here

When accessing that resource we will notice that it’s a float value which isn’t that pretty to show on a screen. However, if we take that value, cut the decimals and make it into a string, it’ll be a lot more user-friendly. To do that, we can:

currentWeather = response.json()["main"]["temp"]
prettierWeather = str(int(currentWeather))+"°C"

That part basically takes the value from the JSON file and changes it from a value that looks like 17.89 to 17°C. That way, we can use some drawing functions later to show that temperature on the screen.

Now that we’ve got the temperature, let’s also figure out how to get an icon loaded and painted on a canvas that we can show on the screen. We will use the icons downloaded beforehand. Copy and paste one of the .svgs (I used wi-thermometer.svg) in the same folder as the script. After that, we want to convert that .svg to a .png file that we can handle with Pillow. To do that, we:

svg2png(url="wi-thermometer.svg", write_to="tempIcon.png", parent_width=200,parent_height=200)

After using the svg2png module to convert the wi-thermometer.svg file to tempIcon.png with a width and height of 200 pixels, we can now make a Pillow canvas and load the icon on it:

screenCanvas = Image.new('RGBA', (640,384), (255,255,255,255))
tempIcon = Image.open("tempIcon.png")
tempIcon.convert("RGBA")
screenCanvas.paste(tempIcon, box=(0,0), mask=tempIcon)

After creating the white 640×384 canvas, opening the image, converting it to RGBA and pasting it at position 0,0 while using itself as a mask, we can now draw the temperature string that we’ve made earlier. We do that with:

drawCanvas = ImageDraw.Draw(screenCanvas)
currentTempFont = ImageFont.truetype(r'OpenSans-ExtraBold.ttf', 100)
drawCanvas.text((200, 10), prettierWeather, fill="black", font=currentTempFont)

So, we created a draw canvas from the first one with the icon on it, loaded the OpenSans-ExtraBold font, size 100 and drew the text on it. Now, it’s time to save our canvas:

screenCanvas.save("testWeatherImage.png", format="PNG")

and then use and modify the Waveshare example code to send the .png file to the screen:

epd = epd7in5.EPD()
epd.init()
epd.Clear(0xFF)
InitialImage = Image.open("testWeatherImage.png")
ShownImage = InitialImage.rotate(180)
epd.display(epd.getbuffer(ShownImage))
epd.sleep()

Luckily, if all went well, you should see a thermometer icon and the temperature in your area next to it on your Waveshare E Ink display. If that didn’t work, let me know so that we can debug it.

After showing your current weather, you might want to get a forecast. Calling the forecast API is done the same as with the normal weather API, the only difference being that the response JSON now contains a list of weather dictionaries. Getting the forecast temperature for three hours from now would look like this:

url = "https://api.openweathermap.org/data/2.5/weather?id=YOURCITYID&APPID=YOURAPIKEY&units=metric"
response = requests.post(url)
completeForecast = response.json()
nextThreeHours = completeForecast["list"][0]["main"]["temp"]

As you probably noticed, the main difference is the fact that the data is kept in an array of dictionaries similar to the single JSON file we got from the weather API. The difference is, every index in the array is a forecast for a moment up to three hours in the future. From what I could gather, it returns the forecast for 00:00, 03:00, 06:00 and so forth, always starting at the closest time in the future to when the request was made. So if I make a request to the forecast API at 05:00, the item at ["list"][0] will be the forecast for 06:00, ["list"][1] will be the forecast for 09:00 and so forth.

That’s about it. Thank you for reading this far and if you’ve tried the stuff above and didn’t manage to make it work, let me know and we’ll figure it out.

Leave a comment