Customising The E-ink Weather Display

With all the pieces in place to allow me to draw to the e-ink screen, I made some 8-bit RAW images and wrote values to the display. It worked, but it looked a bit crusty, not least because the Framebuffer.text method for putting text onto the screen is a fixed size, and a fixed font. Remember that I’m aiming for something like this, which uses a few different font sizes:

As with so many things, this has been solved before, with a piece of work that I think is absolute genius going by the catchy title of “font_to_py“. Basically, it’s a Python command line tool that inputs a font file (like a ttf) and some parameters, such as pixel size, and outputs a Python file containing all the data necessary to render the font as byte data. The files are very small too, with a 20 pixel font “compiling” down to 17kb.

Then you just import the Python files, which in my case were three different sizes of Calibri, and the functions to draw the font to a Framebuffer:

import calibri_36, calibri_15, calibri_12
from writer import Writer

And to draw to the display something like:

wri = Writer(display_buffer, calibri_36)
Writer.set_textpos(display_buffer, text_top, text_left)
wri.printstring(max_daily, True)

To print the value of the maximum daily temperature to the screen (via the Framebuffer). Somewhat strangely, the coordinates are passed in as (top, left) rather than (x, y). I also tweaked writer.py slightly to make sure it handled transparency (i.e. 0 pixels), by changing:

self.device.blit(fbc, s.text_col, s.text_row)

to

self.device.blit(fbc, s.text_col, s.text_row, 1 if invert else 0)

in the _printchar method.

The fonts still looked a little weedy, though, and rather than make a bold version of Calibri my own slightly hacky method was just to print the same characters four times over, offset by 1 pixel each time:

Writer.set_textpos(display_buffer, text_top, text_left)
wri.printstring(max_daily, True)
Writer.set_textpos(display_buffer, text_top, text_left - 1)
wri.printstring(max_daily, True)
Writer.set_textpos(display_buffer, text_top + 1, text_left)
wri.printstring(max_daily, True)
Writer.set_textpos(display_buffer, text_top + 1, text_left - 1)
wri.printstring(max_daily, True)

The results were good enough.

For degree symbols I used Framebuffer.ellipse, and to calculate the position to draw the symbol in writer.py even provides a very useful “how wide is this piece of text?” function:

text_width = get_string_display_width(wri, max_daily)
display_buffer.ellipse(text_left + text_width + 4, text_top + 5, 4, 4, 0)
display_buffer.ellipse(text_left + text_width + 4, text_top + 5, 3, 3, 0)
display_buffer.ellipse(text_left + text_width + 4, text_top + 5, 2, 2, 0)

Note that the ellipse (circle) is also drawn three times to create a faux bold effect.

The last part was some trigonometry as I wanted to display wind direction as an arrow in a circle.

Putting it all together resulted in something usable, albeit with some pixel tweaking required to sharpen some of the smaller images:

An epaper display showing temperature, weather summary, wind direction and rainfall duration

The last thing was to schedule it to update every hour. I experimented with lightsleep and deepsleep, which are supposedly low power modes, but the Pico just never seemed to wake up (and from Googling that appears to be a known issue), so I just took to a sleep function:

HOURS_SLEEP = 1
sleep_time = 60 * 60 * HOURS_SLEEP

Having made something functional, I now had a list of things I wanted to improve:

  1. The size. It’s a little bit too small. I decided to order a 2.9inch e-paper display, and this time get one with the right HAT
  2. The four hour summary is useful, but the weather at 7am is irrelevant once that time of day is passed. I wanted to change it to a rolling twenty-hour period
  3. What happens if I hit an error? I might think the weather’s the same every day and not notice the program has crashed. I needed to show the date/time of last update, and the “feels like” temperature is probably the least useful metric to replace it with
  4. The sleep function updated after an hour, but every update took maybe twenty seconds. If I was going to show update time, I wanted it to be on the hour, not to drift by a couple of minutes per day

I placed an order for another Pico (W, with headers, of course) and the larger e-ink display and waited.