We'll need to make sure we have these libraries installed. (Check out this link on installing libraries if needed.)
- adafruit_bitmap_font
- adafruit_bus_device
- adafruit_display_text
- adafruit_fakerequests.mpy
- adafruit_portalbase
- adafruit_matrixportal
- adafruit_esp32spi
- adafruit_io
- adafruit_matrixportal
- adafruit_requests.mpy
- adafruit_connection_manager.mpy
- adafruit_miniqr.mpy
- adafruit_pixelbuf.mpy
- adafruit_ticks.mpy
- neopixel.mpy
- simpleio.mpy

Connect to the Internet
Once you have CircuitPython setup and libraries installed we can get your board connected to the Internet. The process for connecting can be found here. This includes important info on the settings.toml file, adafruit IO keys, and more!
Text Editor
Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide including info on how to use the REPL/serial terminal.
Alternatively, you can use any text editor that saves simple text files.
Code
Copy the code from the code-block below and paste it into the Mu editor and save it to your Metro M4 Airlift as code.py (or copy code.py from the zip file and place on the CIRCUITPY drive).
# SPDX-FileCopyrightText: 2020 John Park for Adafruit Industries # # SPDX-License-Identifier: MIT # Metro Matrix Clock # Runs on Airlift Metro M4 with 64x32 RGB Matrix display & shield from os import getenv import time import board import displayio import terminalio from adafruit_display_text.label import Label from adafruit_bitmap_font import bitmap_font from adafruit_matrixportal.network import Network from adafruit_matrixportal.matrix import Matrix BLINK = True DEBUG = False # Get WiFi details, ensure these are setup in settings.toml ssid = getenv("CIRCUITPY_WIFI_SSID") password = getenv("CIRCUITPY_WIFI_PASSWORD") if None in [ssid, password]: raise RuntimeError( "WiFi settings are kept in settings.toml, " "please add them there. The settings file must contain " "'CIRCUITPY_WIFI_SSID', 'CIRCUITPY_WIFI_PASSWORD', " "at a minimum." ) print(" Metro Minimal Clock") print("Time will be set for {}".format(getenv("timezone"))) # --- Display setup --- matrix = Matrix() display = matrix.display network = Network(status_neopixel=board.NEOPIXEL, debug=False) # --- Drawing setup --- group = displayio.Group() # Create a Group bitmap = displayio.Bitmap(64, 32, 2) # Create a bitmap object,width, height, bit depth color = displayio.Palette(4) # Create a color palette color[0] = 0x000000 # black background color[1] = 0xFF0000 # red color[2] = 0xCC4000 # amber color[3] = 0x85FF00 # greenish # Create a TileGrid using the Bitmap and Palette tile_grid = displayio.TileGrid(bitmap, pixel_shader=color) group.append(tile_grid) # Add the TileGrid to the Group display.root_group = group if not DEBUG: font = bitmap_font.load_font("/IBMPlexMono-Medium-24_jep.bdf") else: font = terminalio.FONT clock_label = Label(font) def update_time(*, hours=None, minutes=None, show_colon=False): now = time.localtime() # Get the time values we need if hours is None: hours = now[3] if hours >= 18 or hours < 6: # evening hours to morning clock_label.color = color[1] else: clock_label.color = color[3] # daylight hours if hours > 12: # Handle times later than 12:59 hours -= 12 elif not hours: # Handle times between 0:00 and 0:59 hours = 12 if minutes is None: minutes = now[4] if BLINK: colon = ":" if show_colon or now[5] % 2 else " " else: colon = ":" clock_label.text = "{hours}{colon}{minutes:02d}".format( hours=hours, minutes=minutes, colon=colon ) bbx, bby, bbwidth, bbh = clock_label.bounding_box # Center the label clock_label.x = round(display.width / 2 - bbwidth / 2) clock_label.y = display.height // 2 if DEBUG: print("Label bounding box: {},{},{},{}".format(bbx, bby, bbwidth, bbh)) print("Label x: {} y: {}".format(clock_label.x, clock_label.y)) last_check = None update_time(show_colon=True) # Display whatever time is on the board group.append(clock_label) # add the clock label to the group while True: if last_check is None or time.monotonic() > last_check + 3600: try: update_time( show_colon=True ) # Make sure a colon is displayed while updating network.get_local_time() # Synchronize Board's clock to Internet last_check = time.monotonic() except RuntimeError as e: print("Some error occured, retrying! -", e) update_time() time.sleep(1)
Using the WiFi and Adafruit IO credentials you entered into the settings.toml file and copied to the CIRCUITPY drive, your sign will connect to your WiFi, connect to Adafruit IO to get the time, and display it!
How it Works
Libraries
First we import the libraries we'll need, including time
, for timekeeping, board
for pin definitions on the Metro M4 Airlift, and displayio
and terminalio
for some of the display and basic font features.
The adafruit_display_text.label
and adafruit_bitmap_font
libraries are imported so we can use the text label commands and incorporate a bitmap font.
Finally adafruit_matrixportal.network
and adafruit_matrixportal.matrix
are imported to handle getting online through the WiFi access point to check the Adafruit IO timeserver, and to handle the lower level matrix display tasks.
Settings
Two user set variables are created next:
BLINK = True
DEBUG = False
These are at the top of the program where it's easy to change them if needed. The BLINK
variable sets weather or not the colon ':' glyph will blink on and off each second.
DEBUG
can be set True
in order to switch from the bitmap font to the simpler terminalio font, as well as to print label bounding box and x, y coordinate values to the serial output, which is helpful when fine-tuning size and position of a bitmap font.
Check for WIFI Credentials
Next, the program checks to make sure there is SSID and password info stored in the settings.toml file that will be needed to go online. If this fails it will print an error message to the serial output.
Display & Network Setup
The matrix display and matrix objects are created next with these commands:
matrix = Matrix() display = matrix.display network = Network(status_neopixel=board.NEOPIXEL, debug=False)
The displayio Group, Bitmap, Palette, and TileGrid are created so we have the proper objects and hierarchy to display the time on the display. For info on this, check out the excellent displayio guide!
BDF Font Load
Now we'll load the BDF font glyphs from the Metro M4 Airlift's storage, in this case the lovely IBM Plex Mono medium in 24 point. (If we're in DEBUG mode, the terminalio font is used instead.)
The clock_label text object is created using the font so we have an object to display the time.
Update Time
The update_time()
function will do all of the heavy lifting of parsing the current time value into discreet hours, minutes, and optional colon chunks that can be fed as arguments into the clock_label.text
command.
This function also handles the blinking logic for the second hand colon, and sets the color of the text label to day vs. evening hours.
Text Centering
There's a very handy command we'll use for centering the text on the display: clock_label.bounding_box
. This returns the x- and y-positions of the label, as well as the width and height of it's bounding box (an imaginary rectangle that encompasses the label).
With this we can then use a couple of simple formulas to set the label's x and y coordinates to be centered on the display. These essentially divide the display in half an the label in half to find their centers.
# Center the label clock_label.x = round(display.width / 2 - bbwidth / 2) clock_label.y = display.height // 2
Initial Display
The last_check
variable is created to keep track of the state of time elapsed since last lookup of Adafruit IO timeserver time.
The update_time()
function is called, and then the clock_label
is appended to the display group so it will appear. At first this will be based on the Metro M4 time, which will be 12:00 when first powered on. But soon, we'll run the network time check to get accurate!
Main Loop
After all of that setup, it's time to get the time! This is the main loop of the program that runs over and over. The first thing to do is check the time if the last_check
variable is either None
or one hour has elapsed (3600
seconds).
The network.get_local_time()
command uses the WiFi connection to synchronize to Adafruit IO timeserver time. Then the last_check
value is reset to the current time.monotonic()
(think of it as a continuously running ticker).
The display is then refreshed with the current time, and the process is repeated every second, taking a look each hour at the timeserver.
Page last edited March 31, 2025
Text editor powered by tinymce.