openweather_graphics.py

This brings up to the openweather_graphics.py file. This code remains largely unchanged from its original state from the original PyPortal Weather display project. There has just been some minor edits to the layout and some of the data that is being pulled.

First off, the purpose of openweather_graphics.py is to take care of all the display setup for the weather data, aka the default display for this project. If you had no alarms setup, then this is the only thing that you would see. It's written a lot like a Python library, since it contains a class with functions inside of it that can then be called elsewhere; in this case in code.py. With this setup, it means that a lot of complex graphical things can be called upon with just a couple of lines of code in code.py, keeping everything nice and neat.

First, just like with other CircuitPython code you're familiar with, libraries are imported. You'll notice that the calendar.py file is being imported, specifically the holidays portion, in order to reference them later.

import time
import json
from calendar import holidays
import displayio
from adafruit_display_text.label import Label
from adafruit_bitmap_font import bitmap_font

This is followed by some setup to allow for our fonts to be imported. We're using four different bitmap fonts. The font family is the same for all of them but the size varies. They all live in the fonts folder, which you'll be able to download in the zipped file from GitHub.

cwd = ("/"+__file__).rsplit('/', 1)[0] # the current working directory (where this file is)

small_font = cwd+"/fonts/EffectsEighty-24.bdf"
medium_font = cwd+"/fonts/EffectsEighty-32.bdf"
weather_font = cwd+"/fonts/EffectsEighty-48.bdf"
large_font = cwd+"/fonts/EffectsEightyBold-68.bdf"

Then we have some arrays that we'll be referencing later in the functions. Basically the month_name and weekday arrays allow for string formatting for the month and days of the week to display on the PyPortal. holiday_checks allows us to bring in the dates of the holidays setup in the calendar.py file and the holiday_greetings are strings that are setup in the calendar.py file and will display on the screen if openweather_graphics.py detects that it is indeed a holiday. We'll go over the function that allows for this to happen, update_date(), in a bit.

month_name = ["Jan.", "Feb.", "Mar.", "Apr.", "May", "June", "July", "Aug.",
              "Sept.", "Oct.", "Nov.", "Dec."]
weekday = ["Mon.", "Tues.", "Wed.", "Thurs.", "Fri.", "Sat.", "Sun."]
holiday_checks = [holidays['new years'][0],holidays['valentines'][0],
                  holidays['halloween'][0],holidays['xmas'][0]]
holiday_greetings = [holidays['new years'][1],holidays['valentines'][1],
                     holidays['halloween'][1],holidays['xmas'][1]]

This brings us to our class definition. The __init__ section basically sets up all of the display components that will be used for our weather graphic. The bitmap fonts are loaded in and the text groups are created for each component along with their location on the screen and color. The default bitmap is also defined here as being weather_background.bmp. That's the fun Retro Weather graphic you see when you boot-up the PyPortal with this code.

The weather_background.bmp file.

class OpenWeather_Graphics(displayio.Group):

    def __init__(self, root_group, *, am_pm=True, celsius=True):
        super().__init__()
        self.am_pm = am_pm
        self.celsius = celsius

        root_group.append(self)
        self._icon_group = displayio.Group()
        self.append(self._icon_group)
        self._text_group = displayio.Group()
        self.append(self._text_group)

        self._icon_sprite = None
        self._icon_file = None
        self.set_icon(cwd+"/weather_background.bmp")

        self.small_font = bitmap_font.load_font(small_font)
        self.medium_font = bitmap_font.load_font(medium_font)
        self.weather_font = bitmap_font.load_font(weather_font)
        self.large_font = bitmap_font.load_font(large_font)
        glyphs = b'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,.: '
        self.small_font.load_glyphs(glyphs)
        self.medium_font.load_glyphs(glyphs)
        self.weather_font.load_glyphs(glyphs)
        self.large_font.load_glyphs(glyphs)
        self.large_font.load_glyphs(('°',))  # a non-ascii character we need for sure
        self.city_text = None
        self.holiday_text = None

        self.time_text = Label(self.medium_font)
        self.time_text.x = 365
        self.time_text.y = 15
        self.time_text.color = 0x5AF78E
        self._text_group.append(self.time_text)

        self.date_text = Label(self.medium_font)
        self.date_text.x = 10
        self.date_text.y = 15
        self.date_text.color = 0x57C6FE
        self._text_group.append(self.date_text)

        self.temp_text = Label(self.large_font)
        self.temp_text.x = 316
        self.temp_text.y = 165
        self.temp_text.color = 0xFF6AC1
        self._text_group.append(self.temp_text)

        self.main_text = Label(self.weather_font)
        self.main_text.x = 10
        self.main_text.y = 258
        self.main_text.color = 0x99ECFD
        self._text_group.append(self.main_text)

        self.description_text = Label(self.small_font)
        self.description_text.x = 10
        self.description_text.y = 296
        self.description_text.color = 0x9FA0A2
        self._text_group.append(self.description_text)

This brings us to our first function, display_weather(). It basically takes the JSON data that the PyPortal will parse (this is taken care of with code.py) and puts the different bits into the different graphical parameters that we just defined in __init__. For example, the weather description in the JSON data from OpenWeatherMaps is loaded as a string into the main_text object. display_weather() will be called in code.py to update the weather data displayed on screen.

Two other important things are happening here related to JSON data. The city.name variable will take our location parameter from code.py, which will have been imported from secrets.py. The location will be printed as a string onto our weather graphic, but it's also related to the JSON data being parsed in general, since OpenWeatherMap is location dependent.

Second, you'll notice a self.set_icon function (which is setup last in this file). This controls what 8-bit weather icon is displayed depending on the weather. These images are stored on an SD card that you'll insert into the PyPortal inside a folder called icons. Why an SD card? Because of the higher resolution of the PyPortal Titano and the number of icons, there simply isn't enough room for them to live onboard along with all of the other graphics, fonts, sounds and code files needed for this project.

Don't forget your SD card with the icons folder saved on it to display your weather icons
def display_weather(self, weather):
        weather = json.loads(weather)

        # set the icon/background
        weather_icon = weather['weather'][0]['icon']
        self.set_icon("/sd/icons/"+weather_icon+".bmp")

        city_name =  weather['name'] + ", " + weather['sys']['country']
        print(city_name)
        if not self.city_text:
            self.city_text = Label(self.medium_font, text=city_name)
            self.city_text.x = 300
            self.city_text.y = 296
            self.city_text.color = 0xCF5349
            self._text_group.append(self.city_text)

        self.update_time()

        main_text = weather['weather'][0]['main']
        print(main_text)
        self.main_text.text = main_text

        temperature = weather['main']['temp'] - 273.15 # its...in kelvin
        print(temperature)
        if self.celsius:
            self.temp_text.text = "%d °C" % temperature
        else:
            self.temp_text.text = "%d °F" % ((temperature * 9 / 5) + 32)

        description = weather['weather'][0]['description']
        description = description[0].upper() + description[1:]
        print(description)
        self.description_text.text = description
        # "thunderstorm with heavy drizzle"

Next are two very similar functions: update_time() and update_date(). update_time() essentially updates the clock displayed on the weather graphic. It will be called in code.py so that the clock stays current on the display. This data is being pulled down using time.localtime(), a Python time function.

def update_time(self):
        """Fetch the time.localtime(), parse it out and update the display text"""
        now = time.localtime()
        hour = now[3]
        minute = now[4]
        format_str = "%d:%02d"
        if self.am_pm:
            if hour >= 12:
                hour -= 12
                format_str = format_str+" PM"
            else:
                format_str = format_str+" AM"
            if hour == 0:
                hour = 12
        time_str = format_str % (hour, minute)
        print(time_str)
        self.time_text.text = time_str

update_date() is similar to update_time(), with the only difference being that instead of the clock, it is updating the date displayed on the weather display. It will also be called in code.py. It's pulling in our month_name and weekday arrays to format the strings that will be displayed. Additionally though, this function is checking to see if the current date being pulled in with time.localtime() matches with any of the dates that we defined in calendar.py for the holidays that we setup. It checks based on the holiday_checks array and then if it is a holiday, the string from calendar.py pulled in with the holiday_greetings array will be displayed right below the date on the weather graphic. We'll go into how these checks are working in a little more detail for code.py, because the same principle concept is being used to check for alarms.

def update_date(self):
        date_now = time.localtime()
        year = date_now[0]
        mon = date_now[1]
        date = date_now[2]
        day = date_now[6]
        today = weekday[day]
        month = month_name[mon - 1]
        date_format_str = " %d, %d"
        shortened_date_format_str = " %d"
        date_str = today+", "+month+date_format_str % (date, year)
        holiday_date_str = month+shortened_date_format_str % (date)
        print(date_str)
        self.date_text.text = date_str
        for i in holiday_checks:
            h = holiday_checks.index(i)
            if holiday_date_str == holiday_checks[h]:
                if not self.holiday_text:
                    self.holiday_text = Label(self.medium_font, max_glyphs=60)
                    self.holiday_text.x = 10
                    self.holiday_text.y = 45
                    self.holiday_text.color = 0xf2f89d
                    self._text_group.append(self.holiday_text)
                self.holiday_text.text = holiday_greetings[h]

Finally, the last function, which we saw called in the display_weather() function, is setup. set_icon() basically allows for our changing weather icon bitmaps to be added to the graphics group that is running for the weather graphics display. You'll see some familiar OnDiskBitmap() setup happening if you have experience with the displayio library.

def set_icon(self, filename):
        """The background image to a bitmap file.
        :param filename: The filename of the chosen icon
        """
        print("Set icon to ", filename)
        if self._icon_group:
            self._icon_group.pop()

        if not filename:
            return  # we're done, no icon desired
        if self._icon_file:
            self._icon_file.close()

        # CircuitPython 6 & 7 compatible
        self._icon_file = open(filename, "rb")
        icon = displayio.OnDiskBitmap(self._icon_file)
        self._icon_sprite = displayio.TileGrid(icon,
                                               pixel_shader=getattr(icon, 'pixel_shader', displayio.ColorConverter()))

        # # CircuitPython 7+ compatible
        # icon = displayio.OnDiskBitmap(filename)
        # self._icon_sprite = displayio.TileGrid(icon, pixel_shader=icon.pixel_shader)

        self._icon_group.append(self._icon_sprite)

This guide was first published on Jan 29, 2020. It was last updated on Jan 29, 2020.

This page (Code Walkthrough - openweather_graphics.py) was last updated on Sep 29, 2021.

Text editor powered by tinymce.