Open Weather Map API Key
This uses the OpenWeatherMap.org to retrieve the weather info through its API. In order to do so, you'll need to register for an account and get an API key.
Go to this link and register for a free account. Once registered, you'll get an email containing your API key, also known as the "openweather token".
Python Code
The code for this project was based off the code available in the PyPortal Weather Station guide, though the graphics portion of this project works quite differently. Go ahead and download the project and we'll go over the code.
The files you will need are code.py, weather_graphics.py, and meteocons.ttf. They should all be placed in the same folder.
# SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
This example queries the Open Weather Maps site API to find out the current
weather for your location... and display it on a eInk Bonnet!
"""
import time
import urllib.request
import urllib.parse
import digitalio
import busio
import board
from adafruit_epd.ssd1675 import Adafruit_SSD1675
from adafruit_epd.ssd1680 import Adafruit_SSD1680
from adafruit_epd.ssd1680b import Adafruit_SSD1680B
from adafruit_epd.ssd1680_legacy import Adafruit_SSD1680_Legacy
from weather_graphics import Weather_Graphics
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
ecs = digitalio.DigitalInOut(board.CE0)
dc = digitalio.DigitalInOut(board.D22)
rst = digitalio.DigitalInOut(board.D27)
busy = digitalio.DigitalInOut(board.D17)
# You'll need to get a token from openweathermap.org, looks like:
# 'b6907d289e10d714a6e88b30761fae22'
OPEN_WEATHER_TOKEN = ""
# Use cityname, country code where countrycode is ISO3166 format.
# E.g. "New York, US" or "London, GB"
LOCATION = "Manhattan, US"
DATA_SOURCE_URL = "http://api.openweathermap.org/data/2.5/weather"
if len(OPEN_WEATHER_TOKEN) == 0:
raise RuntimeError(
"You need to set your token first. If you don't already have one, you can register for a free account at https://home.openweathermap.org/users/sign_up"
)
# Set up where we'll be fetching data from
params = {"q": LOCATION, "appid": OPEN_WEATHER_TOKEN}
data_source = DATA_SOURCE_URL + "?" + urllib.parse.urlencode(params)
# Initialize the Display
display = Adafruit_SSD1680B( # Newer eInk Bonnet (GDEY0213B74 display)
#display = Adafruit_SSD1680_Legacy(122, 250, # pre-2024 SSD1680 Bonnet
#display = Adafruit_SSD1680( # Old eInk Bonnet ssd1680
#display = Adafruit_SSD1675( # Older eInk Bonnet ssd1675
# 122, 250, spi, cs_pin=ecs, dc_pin=dc, sramcs_pin=None, rst_pin=rst, busy_pin=busy,
120, 250, spi, cs_pin=ecs, dc_pin=dc, sramcs_pin=None, rst_pin=rst, busy_pin=busy,
)
display.rotation = 1
gfx = Weather_Graphics(display, am_pm=True, celsius=False)
weather_refresh = None
while True:
# only query the weather every 10 minutes (and on first run)
if (not weather_refresh) or (time.monotonic() - weather_refresh) > 600:
response = urllib.request.urlopen(data_source)
if response.getcode() == 200:
value = response.read()
print("Response is", value)
gfx.display_weather(value)
weather_refresh = time.monotonic()
else:
print("Unable to retrieve data at {}".format(url))
gfx.update_time()
time.sleep(300) # wait 5 minutes before updating anything again
To start with, look for this line in the code and be sure to enter your OPEN_WEATHER_TOKEN:
# You'll need to get a token from openweathermap.org, looks like: # 'b6907d289e10d714a6e88b30761fae22' OPEN_WEATHER_TOKEN = ""
Next, look for this line in the code and set the LOCATION variable to your location:
# Use cityname, country code where countrycode is ISO3166 format. # E.g. "New York, US" or "London, GB" LOCATION = "Manhattan, US"
To run the example, upload code.py, weather_graphics.py, and meteocons.ttf to the same directory and use the following command:
python3 code.py
You should see an output similar to this:
Automation Changes
If you would like to run the weather script in a Cron job or to run on start up, you may find it easier to move the meteocons.ttf file to a more central place.
A good place to put it would be to create a folder named meteocons inside of in /usr/share/fonts/truetype and then move the meteocons.ttf file inside of that folder.
You'll also need to change the location of the file inside of weather_graphics.py.
How It Works
There are two files that are used. First is the main file, which is the one you will use to to run the example.
Main File
First start by loading any libraries that are used. The notable libraries are urllib, adafruit_epd, weather_graphics. The library called urllib is a built-in library used for retrieving and parsing data from the internet. The adafruit_epd library is used to initialize and write data to the ePaper Display, and the weather_graphics is the portion of code that will handle any graphics.
import time import urllib.request import urllib.parse import digitalio import busio import board from adafruit_epd.ssd1680 import Adafruit_SSD1680Z from adafruit_epd.ssd1675 import Adafruit_SSD1675 from adafruit_epd.ssd1680 import Adafruit_SSD1680 from weather_graphics import Weather_Graphics
Now to set up SPI and any pins used. If you are using the EInk bonnet or have wired it up like in the setup page, you shouldn't need to change anything unless you are using a different board than the Raspberry Pi.
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) ecs = digitalio.DigitalInOut(board.CE0) dc = digitalio.DigitalInOut(board.D22) rst = digitalio.DigitalInOut(board.D27) busy = digitalio.DigitalInOut(board.D17)
The next section contains the parameters used for connecting to Open Weather. You should have already filled in the token and location, and if not, take a look at the beginning of this page.
# You'll need to get a token from openweathermap.org, looks like: # 'b6907d289e10d714a6e88b30761fae22' OPEN_WEATHER_TOKEN = "" # Use cityname, country code where countrycode is ISO3166 format. # E.g. "New York, US" or "London, GB" LOCATION = "Manhattan, US" DATA_SOURCE_URL = "http://api.openweathermap.org/data/2.5/weather"
The next section just checks to make sure you've added the token and assembles the URL with the parameters.
if len(OPEN_WEATHER_TOKEN) == 0:
raise RuntimeError(
"You need to set your token first. If you don't already have one, you can register for a free account at https://home.openweathermap.org/users/sign_up"
)
# Set up where we'll be fetching data from
params = {"q": LOCATION, "appid": OPEN_WEATHER_TOKEN}
data_source = DATA_SOURCE_URL + "?" + urllib.parse.urlencode(params)
After that is to set up the ePaper display and set the rotation. If you are using an older eInk Bonnet, you will need to comment out the line for the newer bonnet and uncomment the one for the older bonnet.
# Initialize the Display
display = Adafruit_SSD1680Z( # New Bonnet ssd1680z [GDEY0213B74]
# display = Adafruit_SSD1680_Legacy( # pre-2024 SSD1680 Bonnet
#display = Adafruit_SSD1680( # Old eInk Bonnet ssd1680
#display = Adafruit_SSD1675( # Older eInk Bonnet ssd1675
# 122, 250, spi, cs_pin=ecs, dc_pin=dc, sramcs_pin=None, rst_pin=rst, busy_pin=busy,
120, 250, spi, cs_pin=ecs, dc_pin=dc, sramcs_pin=None, rst_pin=rst, busy_pin=busy,
)
display.rotation = 1
Next is to initialize the Graphics library and initialize the weather_refresh variable with a default value. The weather_refresh variable is used to determine the last time that the weather data was refreshed so that there can be 2 different timing loops for weather and time.
gfx = Weather_Graphics(display, am_pm=True, celsius=False) weather_refresh = None
Finally, the main code loop checks to see if the weather data has been retrieved at all or that at least 600 seconds (10 minutes) have passed since the last time it was refreshed. If either condition is met, it uses urllib to get the data from the Open Weather URL. It then updates the time (as well as the display) using the weather graphics and waits for 300 seconds (5 minutes) so that the display isn't refreshed too often, which can cause the eInk displays to fail prematurely.
while True:
# only query the weather every 10 minutes (and on first run)
if (not weather_refresh) or (time.monotonic() - weather_refresh) > 600:
response = urllib.request.urlopen(data_source)
if response.getcode() == 200:
value = response.read()
print("Response is", value)
gfx.display_weather(value)
weather_refresh = time.monotonic()
else:
print("Unable to retrieve data at {}".format(url))
gfx.update_time()
time.sleep(300) # wait 5 minutes before updating anything again
# SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
#
# SPDX-License-Identifier: MIT
from datetime import datetime
import json
from PIL import Image, ImageDraw, ImageFont
from adafruit_epd.epd import Adafruit_EPD
small_font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16
)
medium_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
large_font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 24
)
icon_font = ImageFont.truetype("./meteocons.ttf", 48)
# Map the OpenWeatherMap icon code to the appropriate font character
# See http://www.alessioatzeni.com/meteocons/ for icons
ICON_MAP = {
"01d": "B",
"01n": "C",
"02d": "H",
"02n": "I",
"03d": "N",
"03n": "N",
"04d": "Y",
"04n": "Y",
"09d": "Q",
"09n": "Q",
"10d": "R",
"10n": "R",
"11d": "Z",
"11n": "Z",
"13d": "W",
"13n": "W",
"50d": "J",
"50n": "K",
}
# RGB Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
class Weather_Graphics:
def __init__(self, display, *, am_pm=True, celsius=True):
self.am_pm = am_pm
self.celsius = celsius
self.small_font = small_font
self.medium_font = medium_font
self.large_font = large_font
self.display = display
self._weather_icon = None
self._city_name = None
self._main_text = None
self._temperature = None
self._description = None
self._time_text = None
def display_weather(self, weather):
weather = json.loads(weather.decode("utf-8"))
# set the icon/background
self._weather_icon = ICON_MAP[weather["weather"][0]["icon"]]
city_name = weather["name"] + ", " + weather["sys"]["country"]
print(city_name)
self._city_name = city_name
main = weather["weather"][0]["main"]
print(main)
self._main_text = main
temperature = weather["main"]["temp"] - 273.15 # its...in kelvin
print(temperature)
if self.celsius:
self._temperature = "%d °C" % temperature
else:
self._temperature = "%d °F" % ((temperature * 9 / 5) + 32)
description = weather["weather"][0]["description"]
description = description[0].upper() + description[1:]
print(description)
self._description = description
# "thunderstorm with heavy drizzle"
self.update_time()
def update_time(self):
now = datetime.now()
self._time_text = now.strftime("%I:%M %p").lstrip("0").replace(" 0", " ")
self.update_display()
def update_display(self):
self.display.fill(Adafruit_EPD.WHITE)
image = Image.new("RGB", (self.display.width, self.display.height), color=WHITE)
draw = ImageDraw.Draw(image)
# Draw the Icon
(font_width, font_height) = icon_font.getbbox(self._weather_icon)[2:4]
draw.text(
(
self.display.width // 2 - font_width // 2,
self.display.height // 2 - font_height // 2 - 5,
),
self._weather_icon,
font=icon_font,
fill=BLACK,
)
# Draw the city
draw.text(
(5, 5), self._city_name, font=self.medium_font, fill=BLACK,
)
# Draw the time
(font_width, font_height) = medium_font.getbbox(self._time_text)[2:4]
draw.text(
(5, font_height * 2 - 5),
self._time_text,
font=self.medium_font,
fill=BLACK,
)
# Draw the main text
(font_width, font_height) = large_font.getbbox(self._main_text)[2:4]
draw.text(
(5, self.display.height - font_height * 2),
self._main_text,
font=self.large_font,
fill=BLACK,
)
# Draw the description
(font_width, font_height) = small_font.getbbox(self._description)[2:4]
draw.text(
(5, self.display.height - font_height - 5),
self._description,
font=self.small_font,
fill=BLACK,
)
# Draw the temperature
(font_width, font_height) = large_font.getbbox(self._temperature)[2:4]
draw.text(
(
self.display.width - font_width - 5,
self.display.height - font_height * 2,
),
self._temperature,
font=self.large_font,
fill=BLACK,
)
self.display.image(image)
self.display.display()
First start by importing any libraries that will be used. The notable library this time is PIL, the Python Imaging library, which is used to handle all the font rendering.
from datetime import datetime import json from PIL import Image, ImageDraw, ImageFont from adafruit_epd.epd import Adafruit_EPD
Next, to set up the fonts. If you would like to change them, you can do so here. Each font is a combination of the font file and the font size in points.
small_font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16
)
medium_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
large_font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 24
)
icon_font = ImageFont.truetype("./meteocons.ttf", 48)
The next step is to create an icon map, which will map the specified OpenWeather icon code to the font character that the meteocons font uses. If you wanted to change the icons, this would be the place to make changes.
# Map the OpenWeatherMap icon code to the appropriate font character
# See http://www.alessioatzeni.com/meteocons/ for icons
ICON_MAP = {
"01d": "B",
"01n": "C",
"02d": "H",
"02n": "I",
"03d": "N",
"03n": "N",
"04d": "Y",
"04n": "Y",
"09d": "Q",
"09n": "Q",
"10d": "R",
"10n": "R",
"11d": "Z",
"11n": "Z",
"13d": "W",
"13n": "W",
"50d": "J",
"50n": "K",
}
Next to define a couple of colors to make the code more readable:
# RGB Colors WHITE = (255, 255, 255) BLACK = (0, 0, 0)
The actual Weather_Graphics class:
class Weather_Graphics:
The first function in the Weather Graphics is code to initialize any variables that will store default values including setting the fonts that were set up earlier.
def __init__(self, display, *, am_pm=True, celsius=True):
self.am_pm = am_pm
self.celsius = celsius
self.small_font = small_font
self.medium_font = medium_font
self.large_font = large_font
self.display = display
self._weather_icon = None
self._city_name = None
self._main_text = None
self._temperature = None
self._description = None
self._time_text = None
The display_weather function formats the data that is passed in into something more readable to humans. As it does, it prints the value to the console to make debugging easier. Once everything is set, it calls the function to update the time.
def display_weather(self, weather):
weather = json.loads(weather.decode("utf-8"))
# set the icon/background
self._weather_icon = ICON_MAP[weather["weather"][0]["icon"]]
city_name = weather["name"] + ", " + weather["sys"]["country"]
print(city_name)
self._city_name = city_name
main = weather["weather"][0]["main"]
print(main)
self._main_text = main
temperature = weather["main"]["temp"] - 273.15 # its...in kelvin
print(temperature)
if self.celsius:
self._temperature = "%d °C" % temperature
else:
self._temperature = "%d °F" % ((temperature * 9 / 5) + 32)
description = weather["weather"][0]["description"]
description = description[0].upper() + description[1:]
print(description)
self._description = description
# "thunderstorm with heavy drizzle"
self.update_time()
The update_time function is pretty short. It just retrieves the time from the Linux operating system using the datetime library, then it formats and sets the value to one of the class variables. Once that is done, it calls the update_display function.
def update_time(self):
now = datetime.now()
self._time_text = now.strftime("%I:%M %p").lstrip("0").replace(" 0", " ")
self.update_display()
The last function in the library is the update_display function, which is used to take the values of each of the class variables and draw them to a canvas. PIL's Image.new() function is used to create a canvas the size of the display and then the draw.text() function is used to draw the text to the canvas in various locations. Once all the text is drawn, the canvas is passed into the EPD library with the self.display.image() function and then the display is refreshed with the self.display.display() function.
def update_display(self):
self.display.fill(Adafruit_EPD.WHITE)
image = Image.new("RGB", (self.display.width, self.display.height), color=WHITE)
draw = ImageDraw.Draw(image)
# Draw the Icon
(font_width, font_height) = icon_font.getsize(self._weather_icon)
draw.text(
(
self.display.width // 2 - font_width // 2,
self.display.height // 2 - font_height // 2 - 5,
),
self._weather_icon,
font=icon_font,
fill=BLACK,
)
# Draw the city
draw.text(
(5, 5), self._city_name, font=self.medium_font, fill=BLACK,
)
# Draw the time
(font_width, font_height) = medium_font.getsize(self._time_text)
draw.text(
(5, font_height * 2 - 5),
self._time_text,
font=self.medium_font,
fill=BLACK,
)
# Draw the main text
(font_width, font_height) = large_font.getsize(self._main_text)
draw.text(
(5, self.display.height - font_height * 2),
self._main_text,
font=self.large_font,
fill=BLACK,
)
# Draw the description
(font_width, font_height) = small_font.getsize(self._description)
draw.text(
(5, self.display.height - font_height - 5),
self._description,
font=self.small_font,
fill=BLACK,
)
# Draw the temperature
(font_width, font_height) = large_font.getsize(self._temperature)
draw.text(
(
self.display.width - font_width - 5,
self.display.height - font_height * 2,
),
self._temperature,
font=self.large_font,
fill=BLACK,
)
self.display.image(image)
self.display.display()
Page last edited March 31, 2026
Text editor powered by tinymce.