Weather Report Part 3

Example Code - Inky pHAT


Today we’ll look at the code (Gist) for interacting with the Inky pHAT eInk display. This will be another quick run-through with more detailed analyses to come in later posts. Enjoy!

The Program

This second script takes the weather data provided by the module in Part 2 and updates the Inky pHAT accordingly. Again, we’ll focus on the most interesting code.

Setting up the Display

1
2
3
4
5
6
try:
    inky_display = auto(ask_user=True, verbose=True)
except TypeError:
    raise TypeError("You need to update the Inky library to >= v1.1.0")

inky_display.set_border(inky_display.BLACK)

After various module imports, we then set up the display itself. Pimoroni, the company behind the Inky pHAT, provides a Python library for interacting with their hardware. I recommend it.

Drawing Preparations

1
2
3
4
5
6
PATH = os.path.dirname(__file__)

# ...

img = Image.open(os.path.join(PATH, "recursive.png")).resize(inky_display.resolution)
draw = ImageDraw.Draw(img)

Earlier in the file, we retrieved the script’s own current directory. We then use this to open an image file from the project that will serve as our background. (This will change as we refactor the code in future posts.) The draw object will serve as our canvas.

1
2
3
4
5
WIDTH=212
HEIGHT=104

# Load the FredokaOne font
font = ImageFont.truetype(FredokaOne, 16)

We set constants for the dimensions of our display. These numbers could likely be fetched dynamically but, since we only need to support the one device we have on hand, these hard numbers will suffice. We also load the font definition needed for the text we will generate.

1
2
def clear_draw():
    draw.rectangle((0, 0, WIDTH, HEIGHT), inky_display.BLACK)

We then define a convenience method, clear_draw, for clearing our canvas. (Note: This makes the background image code from earlier redundant.) Given how short this method ending up being, I’ll likely inline it during the refactor.

The Update Loop

All of the code after this point falls into the same infinite loop. I plan to extract much of this code into separate methods in order to simplify the loop itself.

1
2
while True:
    current_time = datetime.now().strftime("%d/%m %H:%M:%S")

We grab the current time, which will go at the top of the display. Useful for seeing if a passive display is up to date or whether it silently went offline some time ago.

Error Handling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    if hasattr(report, "error_code"):
        text = "Time: {}\nError: {}\n{}".format(current_time, report.error_code, report.message)

        print(text)
        print(report.response)

        clear_draw()
        draw.text((4, 12), text, inky_display.WHITE, font=font)

        # Display the error on Inky pHAT
        inky_display.set_image(img)
        inky_display.show()

        thirty_minutes = 60*30

        time.sleep(thirty_minutes)

        continue

If something went wrong with retrieving the weather data then we render and display an error message. This use of hasattr to accommodate ducktyping will be one of the first things to go when we refactor this code, though this hacky approach did get us a functional prototype.

The Weather Log

1
2
3
4
5
6
7
8
9
10
11
12
    weather_log = "Time: {}\nTemp: {}F\nFeels Like: {}F\nHigh: {}F/Low: {}F\nHumidity: {}".format(
        current_time,
        report.current_temperature,
        report.feels_like,
        report.high,
        report.low,
        report.humidity
    )

    print(weather_log)

    text = "{}".format(weather_log)

If everything went well, we then prepare our textual version of the weather report. We also print that text for debugging purposes. (The text assignment line isn’t doing anything special; it’s left over from the earlier iteration of this code.)

Display the Log

1
2
3
4
5
6
    clear_draw()
    draw.text((4, 4), text, inky_display.WHITE, font=font)

    # Display the weather data on Inky pHAT
    inky_display.set_image(img)
    inky_display.show()

We then clear our canvas, draw the weather report text onto it, set the image to be displayed on the Inky pHAT, and finally perform the actual display update.

1
2
3
    ten_minutes = 60*10

    time.sleep(ten_minutes)

With the display updated, our device takes a well-deserved nap. The ten-minute refresh cycle was arbitrary. I may lengthen it in the future, given the slow rate of temperature change where I live and how big of a change it would take for me to alter my outdoor wardrobe.

Summary

And that’s the display update side of the code! When we refactor this, I plan to break all of the Inky pHAT-specific code into its own module. That will make it easier to see how this script connects the device to the weather data. Look forward to that in a later installment!

The Complete File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import glob
import os
from sys import exit
import time
from datetime import datetime

from font_fredoka_one import FredokaOne
from inky.auto import auto
from PIL import Image, ImageDraw, ImageFont

from weather_report import fetch_weather

# Get the current path
PATH = os.path.dirname(__file__)

# Set up the display
try:
    inky_display = auto(ask_user=True, verbose=True)
except TypeError:
    raise TypeError("You need to update the Inky library to >= v1.1.0")

inky_display.set_border(inky_display.BLACK)

# Create a new canvas to draw on
# Size 212x104
img = Image.open(os.path.join(PATH, "recursive.png")).resize(inky_display.resolution)
draw = ImageDraw.Draw(img)

WIDTH=212
HEIGHT=104

# Load the FredokaOne font
font = ImageFont.truetype(FredokaOne, 16)

def clear_draw():
    draw.rectangle((0, 0, WIDTH, HEIGHT), inky_display.BLACK)

while True:
    current_time = datetime.now().strftime("%d/%m %H:%M:%S")

    report = fetch_weather()

    if hasattr(report, "error_code"):
        text = "Time: {}\nError: {}\n{}".format(current_time, report.error_code, report.message)

        print(text)
        print(report.response)

        clear_draw()
        draw.text((4, 12), text, inky_display.WHITE, font=font)

        # Display the error on Inky pHAT
        inky_display.set_image(img)
        inky_display.show()

        thirty_minutes = 60*30

        time.sleep(thirty_minutes)

        continue

    weather_log = "Time: {}\nTemp: {}F\nFeels Like: {}F\nHigh: {}F/Low: {}F\nHumidity: {}".format(
        current_time,
        report.current_temperature,
        report.feels_like,
        report.high,
        report.low,
        report.humidity
    )

    print(weather_log)

    text = "{}".format(weather_log)

    clear_draw()
    draw.text((4, 4), text, inky_display.WHITE, font=font)

    # Display the weather data on Inky pHAT
    inky_display.set_image(img)
    inky_display.show()

    ten_minutes = 60*10

    time.sleep(ten_minutes)