Skip to content

Instantly share code, notes, and snippets.

@ScientificProgrammer
Last active October 22, 2023 01:56
Show Gist options
  • Save ScientificProgrammer/bd08a1474c6e5f0a8f102b35ac2533f1 to your computer and use it in GitHub Desktop.
Save ScientificProgrammer/bd08a1474c6e5f0a8f102b35ac2533f1 to your computer and use it in GitHub Desktop.
A tiny weather display console using a Pi Zero W and PaPiRus 2" (200x96) eInk display

FILENAME: README.md

PURPOSE

This code uses a Raspberry Pi Zero W to query the Open Weather Maps API, retrieve the current weather data for a location specified by the user, formats the response, and then displays it on a PaPiRus eInk display.

BACKGROUND

This code was based on code taken from a tutorial on the Adafruit website. The original code was written to work with an Adafruit eInk display. This version was modified to work with a 2.0" (200x96) PaPiRus eInk display attached to a Raspberry Pi Zero W.

PROJECT FILES

ID FILENAME DESCRIPTION
1 README.md This README file.
2 weather.py Contains the primary code to be edited and called by the user.
3 weather_graphics.py Contains the primary driver display code. Generally, the user should not need to edit this code.
4
DejaVuSans.ttf
DejaVuSans.ttf
DejaVuSans.ttf
DejaVuSans-Bold.ttf
Display Fonts - All of these will be located in /usr/share/fonts/truetype/dejavu/
5
./meteocons.ttf
Weather icons

PREREQUISITES: Please read before going any further

Setting up the PaPiRus API for the eInk screen

Prior to running this code, you will need to install the PaPiRus eInk API. The full documentation for this API is available via https://github.com/PiSupply/PaPiRus. However, if you want to try to do the quick install of the API, try running the following command and make sure that it completes with no errors:

 curl -sSL https://pisupp.ly/papiruscode | sudo bash

Getting the icons required for use in weather_graphics.py

See https://www.alessioatzeni.com/meteocons/ for icons

Download and unzip the meteo icons and fonts

curl --remote-name https://www.alessioatzeni.com/meteocons/res/download/meteocons-icons.zip && unzip meteocons-icons.zip
curl --remote-name https://www.alessioatzeni.com/meteocons/res/download/meteocons-font.zip && unzip meteocons-font.zip

Copy the meteocons.ttf to the current working directory

cp ./meteocons-font/FONT/Desktop-font/meteocons.ttf ./

PARTS LIST

ITEM NOTES
PaPiRus eInk Display
For this project, I used the 2" display, which has a resolution of 200x96 pixels. Although I used dynamic variables that queried the display width and height, the actual display code will likely need to be modified if a different sized screen is used, especially if it is smaller than 2".
Raspberry Pi Zero W
  1. Option 1
  2. Option 2
Option 1 - (with header pins installed)
This model, which was the one used for this project, has the header pins installed. The base model Pi Zero W could be used, but only after the header pins have been installed.

Option 2 - Base model - no pre-installed header pins To use this model, header pins will need to be installed first.
PaPiRus Github Repo Contains installation instructions and sample code for the API.

INSTRUCTIONS FOR UPDATING WEATHER AND DISPLAY

MANUAL PROCESS: Making a single call to the code

Call python3 weather.py at the command line.

Be sure that the Python interpreter can find the font files called in weather_graphics.py. The best way to ensure the font files are available is to specify the full path name.

For system fonts, specifying a fixed path is not a problem. However, for specialized fonts, such as meteocons.ttf, the easiest way to guarantee they are found is to place the font file in the same folders as weather.py & weather_graphics.py, and issue the call to the Python interpreter as follows:

    cd /INSERT-CORRECT-PATH-NAME-HERE/ && python3 weather.py

AUTOMATED PROCESS: Using the cron daemon

At the command line, run the following command to start the crontab file editor. If you have not run it previously, you will be asked to select your text editor. If you are unsure of which one to use, select the nano option.

crontab -e

Add the following lines of code. This code will run the weather update code every 15 minutes during the hours of 7 AM - 10 PM. However, during the hours of 11 PM - 6 AM, it will only run the code every hour. You can change these times to suit your own preferences.

# min          hr     dom  mon dow   command
00,15,30,45    7-22   *    *   *     cd /home/pi/projects/PaPiRus/WeatherStation/python/ && python3 weather.py
00             0-6    *    *   *     cd /home/pi/projects/PaPiRus/WeatherStation/python/ && python3 weather.py
00             23     *    *   *     cd /home/pi/projects/PaPiRus/WeatherStation/python/ && python3 weather.py

ADDITIONAL NOTES

Most significant changes between this code and the original tutorial code (Optional)

Here are the biggest changes between this code and the original code in the Adafruit tutorial.

  1. The original code was written for an Adafruit branded eInk bonnet. This code was written for the PaPiRus eInk display.
  2. The original code contained a while loop in weather.py. For this code, I chose to get rid of the while loop and use a cron job to update the display periodically. I made this decision because
    1. the cron daemon is perfectly suited for tasks that run at scheduled intervals, and
    2. the cron approach will be easier and more robust.
    3. With the original code, a user would need to explicitly run the code after every reboot. Additionally, after manually running the code, the user would need to either
      1. stay logged in permanently, which will fail as soon as their terminal connection is broken, or
      2. the user would need to install a package like screen, which can continue to run jobs even if a user's terminal connection is broken.
    4. By using cron, all of the aforementioned problems are obviated.
  3. I added a smaller font (tiny_font), which allowed me to add some additional weather elements to the screen, specifically, the day and date, the seconds to the time, and the relative humidity.
    1. I believe there is enough screen real estate to be able to display the feels_like data, which the code does retrieve and stores.
    2. However, I don't display it because the current displayed items would need to be repositioned in an optimal way. That task will be done in a future update.

"""
FILENAME: weather.py
File 1 of 2
See README.md for more details
BACKGROUND
Taken from https://learn.adafruit.com/raspberry-pi-e-ink-weather-station-using-python/weather-station-code
This example queries the Open Weather Maps site API to find out the current
weather for your location... and display it on a PaPiRus eInk display.
The original code was written to work with an Adafruit eInk display. This version was
modified to work with a 2.0" (200x96) PaPiRus eInk display attached to a Raspberry Pi Zero W.
eInk Display: https://www.adafruit.com/product/3335
Raspberry Pi Zero W: https://www.adafruit.com/product/3708 This model has the header pins installed.
https://www.adafruit.com/product/3400 Header pins are not included with this model.
PaPiRus Github Repo: https://github.com/PiSupply/PaPiRus Contains installation instructions and sample code for the API.
PROJECT FILES
1) weather.py
2) weather_graphics.py
INSTRUCTIONS:
call 'python3 weather.py' at the command line.
"""
import json
import datetime
import time
import urllib.request
import urllib.parse
from weather_graphics import Weather_Graphics
from papirus import Papirus
# You'll need to get a token from openweathermap.org, looks like:
# 'b6907d289e10d714a6e88b30761fae22'
OPEN_WEATHER_TOKEN = "b6907d289e10d714a6e88b30761fae22" # Be sure to get your own token. This one won't work.
URL_SUCCESS_CODE = 200
# Use cityname, country code where countrycode is ISO3166 format.
# E.g. "New York, US" or "London, GB"
# In the US, a ZIP code will usually work, too.
#LOCATION = "New York, New York"
LOCATION = "10001, US"
DATA_SOURCE_URL = "https://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
DISP_ROTATION = 0
img_display_screen = Papirus(rotation = DISP_ROTATION)
gfx = Weather_Graphics(img_display_screen, am_pm = True, celsius = False)
weather_refresh = None
response = urllib.request.urlopen(data_source)
if response.getcode() == URL_SUCCESS_CODE:
value = response.read()
print("Response is", json.dumps(json.loads(value), indent = 2, sort_keys = True))
gfx.display_weather(value)
print("")
print("URL=", data_source)
print("Last refresh: ", datetime.datetime.now().strftime("%I:%M:%S %p"))
weather_refresh = time.monotonic()
else:
print("Unable to retrieve data at {}".format(url))
"""
FILENAME: weather_graphics.py
File 2 of 2
This example queries the Open Weather Maps site API to find out the current
weather for your location... and display it on a PaPiRus eInk display!
Taken from https://learn.adafruit.com/raspberry-pi-e-ink-weather-station-using-python/weather-station-code
PROJECT FILES
1) weather.py
2) weather_graphics.py
INSTRUCTIONS:
call 'python3 weather.py' at the command line.
See README.md for more details.
"""
from datetime import datetime
import json
from PIL import Image, ImageDraw, ImageFont
from papirus import Papirus
tiny_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12)
small_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.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", 40)
# Map the OpenWeatherMap icon code to the appropriate font character
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",
}
# Conversion from Kelvins to deg C
KELVINS_TO_DEG_C = 273.15
# 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.tiny_font = tiny_font
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)
# set the icon/background
self._weather_icon = ICON_MAP[weather["weather"][0]["icon"]]
city_name = weather["name"] + ", " + weather["sys"]["country"]
print("city_name:\t", city_name)
self._city_name = city_name
main = weather["weather"][0]["main"]
print("main:\t\t", main)
self._main_text = main
humidity = weather["main"]["humidity"]
self._humidity = "R/H=%d%%" % humidity
feels_like = weather["main"]["feels_like"] - KELVINS_TO_DEG_C # Convert kelvins to degrees C
temperature = weather["main"]["temp"] - KELVINS_TO_DEG_C # Convert kelvins to degrees C
if self.celsius:
self._temperature = "%d °C" % temperature
self._feels_like = "%d °C" % feels_like
else:
self._temperature = "%d °F" % ((temperature * 9 / 5) + 32)
self._feels_like = "%d °F" % ((feels_like * 9 / 5) + 32)
print("temperature:\t", self._temperature)
print("feels_like:\t", self._feels_like)
description = weather["weather"][0]["description"]
description = description[0].upper() + description[1:]
print("description:\t", description)
self._description = description
# Examples: "thunderstorm with heavy drizzle", "Clear sky", "Cloudy sky"
self.update_time()
def update_time(self):
now = datetime.now()
#self._time_text = now.strftime("%I:%M %p").lstrip("0").replace(" 0", " ")
self._date_text = now.strftime("%a %b %d, %Y")
self._time_text = now.strftime("%I:%M:%S %p")
self.update_display()
def update_display(self):
image = Image.new("RGB", (200, 96), color=WHITE)
draw = ImageDraw.Draw(image)
# Draw the Icon
(font_width, font_height) = icon_font.getsize(self._weather_icon)
draw.text(
(
self.display.width - font_width - 2, # x-position
0, # y-position
),
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 date
(font_width, font_height) = medium_font.getsize(self._date_text)
draw.text(
(5, font_height + 5),
self._date_text,
font=self.tiny_font,
fill=BLACK,
)
# Draw the time
(font_width, font_height) = medium_font.getsize(self._time_text)
yheight = font_height + 21
draw.text(
(5, yheight),
self._time_text,
font=self.tiny_font,
fill=BLACK,
)
# Draw the relative humidity
(font_width, font_height) = tiny_font.getsize(self._humidity)
draw.text(
(self.display.width - font_width - 5, yheight),
self._humidity,
font=self.tiny_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.display(image)
self.display.update()
@ScientificProgrammer
Copy link
Author

Hi @Gassolo:

So long as

  1. you could get the proper drivers from the vendor, and
  2. they have a reasonable API,

you should be able to start with my code base and then make the necessary modifications without too much trouble.

My recommendation is to contact the vendor and ask them for sample code. In the page that you linked for me, they state they are happy to provide it. Then, once you have the sample code, compare their sample code with the code base for the PaPiRus display that I used, which is available at https://github.com/PiSupply/PaPiRus.

So long as they have corresponding functionality, then I think you'll see that modifying my code to work with their display won't be too difficult. The obvious places where you'll need to make changes to my code are line 36 in weather.py and line 23 in weather_graphics.py. Instead of importing the Papirus module, you'd be importing whatever module the vendor for the other screen has specified.

You'll also need to modify line 62 in weather.py.

You will almost certainly have to make some other tweaks to the code in weather_graphics.py, but, the required changes should be very obvious once you understand how to use the API for your vendor's eInk screen to write text and graphics to the screen.

Good luck, and let me know if I can help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment