Here is the project code along with some additional setup needed.

Weather Icons

The icons used in this project were inspired by this other EPD based weather project - eInk / ePaper Weather Station. They are the excellent set of Meteocons , which the author has made freely available. For this project, we converted them to gray scale, scaled them as needed, and converted to bitmap.

Additional Library

In addition to all the MagTag specific libraries mentioned and installed previously, one additional library is needed for this project.  Make sure you have a copy of this in your CIRCUITPY/lib folder as well:

  • adafruit_imageload - Used to load BMP files into memory.

It is available in the bundle along with all the other libraries. This project uses that library to load the weather icon bitmap files.

Open Weather Maps API Key

We'll be using to retrieve the weather info through its API. In order to do so, you'll need to register for an account and get your API key.

Go to this link and register for a free account. Once registered, you'll get an email containing your API key, also known as the "openweather token".

Copy and paste this key into your file that is on the root level of your CIRCUITPY drive. Name the entry openweather_token, ex:

'openweather_token' : 'my_openweather_token',

It'll look something like this

'openweather_token' : 'b218dde228d7be12f16bf4640208b9f5',

this is not a valid token, you have to get your own!

It may take some time for your Open Weather Maps API key to become active - tens of minutes or even hours.

Set Location

We will also use to set your location. To do so, add new entry called openweather_location and specify your city name and country code, for example:

'openweather_location' : 'Thief River Falls, US',

You can verify your town or city is valid by visiting and typing it into the box, if you get a weather report, then that's a known city!

Secrets Summary

Your resulting file should looking something like the example below, but with your specific values. It's OK if there are more entries - for other services, etc. But you need at least the ones shown here.

secrets = {
    'ssid' : 'myssid',
    'password' : 'mypassword',
    'openweather_token' : 'my_openweather_token',
    'openweather_location' : 'Thief River Falls, US',
Don't forget to end each line with a comma.

Bitmap Files

You'll also need a copy of the following bitmap files in your CIRCUITPY/bmps folder. If you download the project zip below, they should be contained in there as well.

This one is the main background:

And these two are used as sprite sheets to provide the weather icons. One provides large icons for today's weather. The other provides smaller icons used for the future forecast.

Project Code

Make sure you've installed CircuitPython and all the required libraries, as well as set up API access to be able to get the local time on the previous pages.

Once ready, click the Download: Project Zip File link below in the code window to get a zip file with all the files needed for the project. Copy from the zip file and place on the CIRCUITPY drive.

If you're having difficulty running this example, it could be because your MagTag CircuitPython firmware or library needs to be upgraded! Please be sure to follow to install the latest CircuitPython firmware and then also replace/update ALL the MagTag-specific libraries mentioned here
import time
import terminalio
import displayio
import adafruit_imageload
from adafruit_display_text import label
from adafruit_magtag.magtag import MagTag
from secrets import secrets

# --| USER CONFIG |--------------------------
METRIC = False  # set to True for metric units
# -------------------------------------------

# ----------------------------
# Define various assets
# ----------------------------
BACKGROUND_BMP = "/bmps/weather_bg.bmp"
ICONS_LARGE_FILE = "/bmps/weather_icons_70px.bmp"
ICONS_SMALL_FILE = "/bmps/weather_icons_20px.bmp"
ICON_MAP = ("01", "02", "03", "04", "09", "10", "11", "13", "50")
DAYS = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
magtag = MagTag()

# ----------------------------
# Backgrounnd bitmap
# ----------------------------

# ----------------------------
# Weather icons sprite sheet
# ----------------------------
icons_large_bmp, icons_large_pal = adafruit_imageload.load(ICONS_LARGE_FILE)
icons_small_bmp, icons_small_pal = adafruit_imageload.load(ICONS_SMALL_FILE)

# /////////////////////////////////////////////////////////////////////////

def get_data_source_url(api="onecall", location=None):
    """Build and return the URL for the OpenWeather API."""
    if api.upper() == "FORECAST5":
        URL = ""
        URL += "q=" + location
    elif api.upper() == "ONECALL":
        URL = ",hourly,alerts"
        URL += "&lat={}".format(location[0])
        URL += "&lon={}".format(location[1])
        raise ValueError("Unknown API type: " + api)

    return URL + "&appid=" + secrets["openweather_token"]

def get_latlon():
    """Use the Forecast5 API to determine lat/lon for given city."""
    magtag.url = get_data_source_url(api="forecast5", location=secrets["openweather_location"])
    magtag.json_path = ["city"]
    raw_data = magtag.fetch()
    return raw_data["coord"]["lat"], raw_data["coord"]["lon"]

def get_forecast(location):
    """Use OneCall API to fetch forecast and timezone data."""
    resp ="onecall", location=location))
    json_data = resp.json()
    return json_data["daily"], json_data["current"]["dt"], json_data["timezone_offset"]

def make_banner(x=0, y=0):
    """Make a single future forecast info banner group."""
    day_of_week = label.Label(terminalio.FONT, text="DAY", color=0x000000)
    day_of_week.anchor_point = (0, 0.5)
    day_of_week.anchored_position = (0, 10)

    icon = displayio.TileGrid(

    day_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
    day_temp.anchor_point = (0, 0.5)
    day_temp.anchored_position = (50, 10)

    group = displayio.Group(x=x, y=y)

    return group

def temperature_text(tempK):
    if METRIC:
        return "{:3.0f}C".format(tempK - 273.15)
        return "{:3.0f}F".format(32.0 + 1.8 * (tempK - 273.15))

def wind_text(speedms):
    if METRIC:
        return "{:3.0f}m/s".format(speedms)
        return "{:3.0f}mph".format(2.23694 * speedms)

def update_banner(banner, data):
    """Update supplied forecast banner with supplied data."""
    banner[0].text = DAYS[time.localtime(data["dt"]).tm_wday][:3].upper()
    banner[1][0] = ICON_MAP.index(data["weather"][0]["icon"][:2])
    banner[2].text = temperature_text(data["temp"]["day"])

def update_today(data, tz_offset=0):
    """Update today info banner."""
    date = time.localtime(data["dt"])
    sunrise = time.localtime(data["sunrise"] + tz_offset)
    sunset = time.localtime(data["sunset"] + tz_offset)

    today_date.text = "{} {} {}, {}".format(
        MONTHS[date.tm_mon - 1].upper(),
    today_icon[0] = ICON_MAP.index(data["weather"][0]["icon"][:2])
    today_morn_temp.text = temperature_text(data["temp"]["morn"])
    today_day_temp.text = temperature_text(data["temp"]["day"])
    today_night_temp.text = temperature_text(data["temp"]["night"])
    today_humidity.text = "{:3d}%".format(data["humidity"])
    today_wind.text = wind_text(data["wind_speed"])
    today_sunrise.text = "{:2d}:{:02d} AM".format(sunrise.tm_hour, sunrise.tm_min)
    today_sunset.text = "{:2d}:{:02d} PM".format(sunset.tm_hour - 12, sunset.tm_min)

def go_to_sleep(current_time):
    """Enter deep sleep for time needed."""
    # compute current time offset in seconds
    hour, minutes, seconds = time.localtime(current_time)[3:6]
    seconds_since_midnight = 60 * (hour * 60 + minutes) + seconds
    # wake up 15 minutes after midnite
    seconds_to_sleep = (24 * 60 * 60 - seconds_since_midnight) + 15 * 60
        "Sleeping for {} hours, {} minutes".format(
            seconds_to_sleep // 3600, (seconds_to_sleep // 60) % 60

# ===========
# U I
# ===========
today_date = label.Label(terminalio.FONT, text="?" * 30, color=0x000000)
today_date.anchor_point = (0, 0)
today_date.anchored_position = (15, 13)

city_name = label.Label(
    terminalio.FONT, text=secrets["openweather_location"], color=0x000000
city_name.anchor_point = (0, 0)
city_name.anchored_position = (15, 24)

today_icon = displayio.TileGrid(

today_morn_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
today_morn_temp.anchor_point = (0.5, 0)
today_morn_temp.anchored_position = (118, 59)

today_day_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
today_day_temp.anchor_point = (0.5, 0)
today_day_temp.anchored_position = (149, 59)

today_night_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
today_night_temp.anchor_point = (0.5, 0)
today_night_temp.anchored_position = (180, 59)

today_humidity = label.Label(terminalio.FONT, text="100%", color=0x000000)
today_humidity.anchor_point = (0, 0.5)
today_humidity.anchored_position = (105, 95)

today_wind = label.Label(terminalio.FONT, text="99m/s", color=0x000000)
today_wind.anchor_point = (0, 0.5)
today_wind.anchored_position = (155, 95)

today_sunrise = label.Label(terminalio.FONT, text="12:12 PM", color=0x000000)
today_sunrise.anchor_point = (0, 0.5)
today_sunrise.anchored_position = (45, 117)

today_sunset = label.Label(terminalio.FONT, text="12:12 PM", color=0x000000)
today_sunset.anchor_point = (0, 0.5)
today_sunset.anchored_position = (130, 117)

today_banner = displayio.Group()

future_banners = [
    make_banner(x=210, y=18),
    make_banner(x=210, y=39),
    make_banner(x=210, y=60),
    make_banner(x=210, y=81),
    make_banner(x=210, y=102),

for future_banner in future_banners:

# ===========
#  M A I N
# ===========
print("Getting Lat/Lon...")
latlon = get_latlon()

print("Fetching forecast...")
forecast_data, utc_time, local_tz_offset = get_forecast(latlon)

update_today(forecast_data[0], local_tz_offset)
for day, forecast in enumerate(forecast_data[1:6]):
    update_banner(future_banners[day], forecast)

time.sleep(magtag.display.time_to_refresh + 1)
time.sleep(magtag.display.time_to_refresh + 1)

go_to_sleep(utc_time + local_tz_offset)
#  entire code will run again after deep sleep cycle
#  similar to hitting the reset button

This guide was first published on Dec 14, 2020. It was last updated on Dec 14, 2020.

This page (Project Code) was last updated on Oct 15, 2021.

Text editor powered by tinymce.