Introduction
Continue from the previous tutorial, now we will make the weather display on e-paper using Raspberry Pi. The data will be obtained from openweathermap.org.
Video
This video shows how to create weather display on e-paper using Raspberry Pi.
Hardware Preparation
This is the list of items used in the video.
Sample Program
This is python3 sample program to display weather data (from openweathermap.org) on e-paper using Raspberry Pi. You may need to install meteocons font with following command:
curl --remote-name https://www.alessioatzeni.com/meteocons/res/download/meteocons-font.zip && unzip meteocons-font.zip
# This code was based on code taken from a tutorial on the Adafruit website. | |
# https://learn.adafruit.com/raspberry-pi-e-ink-weather-station-using-python | |
# | |
# e-Paper: | |
# – https://my.cytron.io/p-universal-e-paper-raw-panel-driver-hat?tracking=idris | |
# – https://my.cytron.io/p-2.9-inch-e-ink-raw-display-panel-tri-color?tracking=idris | |
# | |
# Register at https://openweathermap.org/ and get the token (API key) | |
# | |
# Download meteocons font and unzip | |
# curl –remote-name https://www.alessioatzeni.com/meteocons/res/download/meteocons-font.zip && unzip meteocons-font.zip | |
import time | |
import urllib.request | |
import urllib.parse | |
import digitalio | |
import busio | |
import board | |
from adafruit_epd.il0373 import Adafruit_IL0373 | |
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.D25) | |
rst = digitalio.DigitalInOut(board.D17) | |
busy = digitalio.DigitalInOut(board.D24) | |
srcs = None | |
# 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 = "Kuala Lumpur, MY" | |
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_IL0373( | |
128, 296, spi, | |
cs_pin=ecs, | |
dc_pin=dc, | |
sramcs_pin=srcs, | |
rst_pin=rst, | |
busy_pin=busy | |
) | |
display.rotation = 3 | |
gfx = Weather_Graphics(display, am_pm=True, celsius=True) | |
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 |
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.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() |
Thank You
References:
Thanks for reading this tutorial. If you have any technical inquiries, please post at Cytron Technical Forum.
“Please be reminded, this tutorial is prepared for you to try and learn.
You are encouraged to improve the code for better application.“