Libraries

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_shapes
  • adafruit_display_text
  • adafruit_esp32spi
  • adafruit_io
  • adafruit_matrixportal
  • adafruit_requests.mpy
  • neopixel.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.

Text Editor

Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get more info in this guide.

Alternatively, you can use any text editor that saves simple text files.

Code

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 halloween_countdown_matrix.py from the zip file and place on the CIRCUITPY drive, then rename it to code.py.

You'll also need to copy the following files to the CIRCUITPY drive. See the graphic at the top of the page as to filenames and where they go):

  • fonts directory, which contains a .bdf font
  • bmp directory, which contains the graphics
  • secrets.py (after you edit to put your WiFi and AIO credentials in the file)
import time
import board
from adafruit_matrixportal.matrixportal import MatrixPortal

EVENT_YEAR = 2020
EVENT_MONTH = 10
EVENT_DAY = 31
EVENT_HOUR = 17
EVENT_MINUTE = 0

FRAME_DURATION = 3
FRAMES = (
    "bmps/jack.bmp",
    "DAYS",
    "bmps/ghost.bmp",
    "HOURS",
    "bmps/bats.bmp",
    "MINUTES",
    "bmps/skull.bmp",
    "bmps/halloween.bmp",
)

EVENT_DAY_IMAGE = "bmps/happy_halloween.bmp"
SYNCHRONIZE_CLOCK = True

# --- Display setup ---
matrixportal = MatrixPortal(status_neopixel=board.NEOPIXEL, debug=True)

current_frame = None

# Create a new label with the color and text selected
matrixportal.add_text(
    text_font="fonts/Arial-12.bdf",
    text_position=(4, (matrixportal.graphics.display.height // 2) - 1),
    text_color=0xEF7F31,
)


def set_time_until(unit=None):
    event_time = time.struct_time(
        (
            EVENT_YEAR,
            EVENT_MONTH,
            EVENT_DAY,
            EVENT_HOUR,
            EVENT_MINUTE,
            0,  # we don't track seconds
            -1,
            -1,
            False,
        )
    )
    remaining = time.mktime(event_time) - time.mktime(time.localtime())
    if remaining <= 0:
        # oh, its event time!
        matrixportal.set_background(EVENT_DAY_IMAGE)
        return
    remaining //= 60
    mins_remaining = remaining % 60
    remaining //= 60
    hours_remaining = remaining % 24
    remaining //= 24
    days_remaining = remaining

    if unit == "DAYS":
        text = "{} day".format(days_remaining)
        if days_remaining != 1:
            text += "s"
    if unit == "HOURS":
        text = "{} hour".format(hours_remaining)
        if hours_remaining != 1:
            text += "s"
    if unit == "MINUTES":
        text = "{} min".format(mins_remaining)
        if mins_remaining != 1:
            text += "s"
    matrixportal.set_text(text)
    matrixportal.set_background(0)


def set_next_frame():
    # pylint: disable=global-statement
    global current_frame

    # Advance to next frame if we already have one
    if current_frame is not None:
        current_frame += 1

    # Loop back or set initial frame
    if current_frame is None or current_frame >= len(FRAMES):
        current_frame = 0

    # Check if Picture or Text
    print(FRAMES[current_frame])
    if FRAMES[current_frame][-4:] == ".bmp":
        matrixportal.set_background(FRAMES[current_frame])
        matrixportal.set_text("")
    else:
        set_time_until(FRAMES[current_frame])


# Simulate the delay in case fetching time is fast
set_next_frame()
start_time = time.monotonic()
if SYNCHRONIZE_CLOCK:
    matrixportal.get_local_time()
while time.monotonic() < start_time + FRAME_DURATION:
    pass

while True:
    set_next_frame()
    time.sleep(FRAME_DURATION)

Adafruit IO Time Server

In order to get the precise time, our project will query the Adafruit IO Internet of Things service for the time. Adafruit IO is absolutely free to use, but you'll need to log in with your Adafruit account to use it. If you don't already have an Adafruit login, create one here.

If you haven't used Adafruit IO before, check out this guide for more info.

Once you have logged into your account, there are two pieces of information you'll need to place in your secrets.py file: Adafruit IO username, and Adafruit IO key. Head to io.adafruit.com and simply click the View AIO Key link on the left hand side of the Adafruit IO page to get this information.

Then, add them to the secrets.py file like this:

Download: file
secrets = {
    'ssid' : 'your_wifi_ssid',
    'password' : 'your_wifi_password',
    'aio_username' : '_your_aio_username_',
    'aio_key' : '_your_big_huge_super_long_aio_key_'
}

How it Works

Libraries

Here's how the code works. First we import the time, board, and adafruit_matrixportal libraries.

Event Time

Then, we'll set the variables for the event year, month, day, hour, and minute. These will be used to calculate the countdown remaining based on the current time.

Download: file
import time
import board
from adafruit_matrixportal.matrixportal import MatrixPortal

EVENT_YEAR = 2020
EVENT_MONTH = 10
EVENT_DAY = 31
EVENT_HOUR = 17
EVENT_MINUTE = 0

Frames

Next, we set the FRAME_DURATION = 3 which means each image or text screen will hold for three seconds. You can adjust this as you like.

Then, we'll define the order in which the images and text will be displayed. This is pretty cool, as it is essentially making a list of which items you want to see appear sequentially. You can mix and match this as you see fit!

Download: file
FRAME_DURATION = 3
FRAMES = (
    "bmps/jack.bmp",
    "DAYS",
    "bmps/ghost.bmp",
    "HOURS",
    "bmps/bats.bmp",
    "MINUTES",
    "bmps/skull.bmp",
    "bmps/halloween.bmp",
)

We'll also set one image aside as the EVENT_DAY_IMAGE = "bmps/happy_halloween.bmp".

MatrixPortal Setup

Next, we'll set up the matrixportal object for the display, and then create a text label.

Download: file
matrixportal = MatrixPortal(status_neopixel=board.NEOPIXEL, debug=True)

current_frame = None

# Create a new label with the color and text selected
matrixportal.add_text(
    text_font="fonts/Arial-12.bdf",
    text_position=(4, (matrixportal.graphics.display.height // 2) - 1),
    text_color=0xEF7F31,
)

Set Time Until Function

This function is used to derive the time until the event based up one the start variables and the current time.

Download: file
def set_time_until(unit=None):
    event_time = time.struct_time(
        (
            EVENT_YEAR,
            EVENT_MONTH,
            EVENT_DAY,
            EVENT_HOUR,
            EVENT_MINUTE,
            0,  # we don't track seconds
            -1,
            -1,
            False,
        )
    )
    remaining = time.mktime(event_time) - time.mktime(time.localtime())
    if remaining <= 0:
        # oh, its event time!
        matrixportal.set_background(EVENT_DAY_IMAGE)
        return
    # secs_remaining = remaining % 60
    remaining //= 60
    mins_remaining = remaining % 60
    remaining //= 60
    hours_remaining = remaining % 24
    remaining //= 24
    days_remaining = remaining

    if unit == "DAYS":
        text = "{} day".format(days_remaining)
        if days_remaining != 1:
            text += "s"
    if unit == "HOURS":
        text = "{} hour".format(hours_remaining)
        if hours_remaining != 1:
            text += "s"
    if unit == "MINUTES":
        text = "{} min".format(mins_remaining)
        if mins_remaining != 1:
            text += "s"
    matrixportal.set_text(text)
    matrixportal.set_background(0)

Set Next Frame Function

We'll use this function to set the next frame to either a bitmap graphic or a text label depending on where we are in the list order.

Download: file
ef set_next_frame():
    global current_frame

    # Advance to next frame if we already have one
    if current_frame is not None:
        current_frame += 1

    # Loop back or set initial frame
    if current_frame is None or current_frame >= len(FRAMES):
        current_frame = 0

    # Check if Picture or Text
    print(FRAMES[current_frame])
    if FRAMES[current_frame][-4:] == ".bmp":
        matrixportal.set_background(FRAMES[current_frame])
        matrixportal.set_text("")
    else:
        set_time_until(FRAMES[current_frame])

Main Loop

The main loop of the program is simple -- it calls the set_next_frame() function and then pauses for the frame duration!

Download: file
while True:
    set_next_frame()
    time.sleep(FRAME_DURATION)

This guide was first published on Sep 22, 2020. It was last updated on Sep 22, 2020.

This page (Code the Halloween Countdown) was last updated on Nov 06, 2020.