Once you've finished setting up your QT Py with CircuitPython, you can access the code and necessary libraries by downloading the Project Bundle.
To do this, click on the Download Project Bundle button in the window below. It will download as a zipped folder.
# SPDX-FileCopyrightText: 2022 Liz Clark for Adafruit Industries # SPDX-License-Identifier: MIT """ CircuitPython Quad-Alphanumeric Display Holiday Countdown. This demo requires a separate file named settings.toml on your CIRCUITPY drive, which should contain your WiFi credentials and Adafruit IO credentials. """ import os import time import ssl import wifi import socketpool import microcontroller import board import adafruit_requests from adafruit_ht16k33.segments import Seg14x4 from adafruit_io.adafruit_io import IO_HTTP, AdafruitIO_RequestError # pylint: disable=unused-import # Date and time of event. Update YEAR, MONTH, DAY, HOUR, MINUTE to match the date and time of the # event to which you are counting down. Update NAME to the name of the event. Update MSG to the # message you'd like to display when the countdown has completed and the event has started. EVENT_YEAR = 2022 EVENT_MONTH = 12 EVENT_DAY = 25 EVENT_HOUR = 0 EVENT_MINUTE = 0 EVENT_NAME = "Christmas" EVENT_MSG = "Merry Christmas * " # The speed of the text scrolling on the displays. Increase this to slow down the scrolling. # Decrease it to speed up the scrolling. scroll_speed = 0.25 # Create the I2C object using STEMMA_I2C() i2c = board.STEMMA_I2C() # Alphanumeric segment display setup using three displays in series. display = Seg14x4(i2c, address=(0x70, 0x71, 0x72)) # Display brightness is a number between 0.0 (off) and 1.0 (maximum). Update this if you want # to alter the brightness of the characters on the displays. display.brightness = 0.2 # The setup-successful message. If this shows up on your displays, you have wired them up # properly and the code setup is correct. display.print("HELLO WORLD") def reset_on_error(delay, error): """Resets the code after a specified delay, when encountering an error.""" print("Error:\n", str(error)) display.print("Error :(") print("Resetting microcontroller in %d seconds" % delay) time.sleep(delay) microcontroller.reset() try: wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) # any errors, reset MCU except Exception as e: # pylint: disable=broad-except reset_on_error(10, e) aio_username = os.getenv("aio_username") aio_key = os.getenv("aio_key") location = os.getenv("aio_location") pool = socketpool.SocketPool(wifi.radio) requests = adafruit_requests.Session(pool, ssl.create_default_context()) # Initialize an Adafruit IO HTTP API object try: io = IO_HTTP(aio_username, aio_key, requests) except Exception as e: # pylint: disable=broad-except reset_on_error(10, e) print("Connected to Adafruit IO") display.print("Connected IO") clock = time.monotonic() event_time = time.struct_time( (EVENT_YEAR, EVENT_MONTH, EVENT_DAY, EVENT_HOUR, EVENT_MINUTE, 0, -1, -1, False) ) scroll_time = 0 while True: try: if (clock + scroll_time) < time.monotonic(): now = io.receive_time() # print(now) # print(event_time) remaining = time.mktime(event_time) - time.mktime(now) # if it's the day of the event... if remaining < 0: # scroll the event message on a loop display.marquee(EVENT_MSG, scroll_speed, loop=True) # calculate the seconds remaining secs_remaining = remaining % 60 remaining //= 60 # calculate the minutes remaining mins_remaining = remaining % 60 remaining //= 60 # calculate the hours remaining hours_remaining = remaining % 24 remaining //= 24 # calculate the days remaining days_remaining = remaining # pack the calculated times into a string to scroll countdown_string = ( "* %d Days, %d Hours, %d Minutes & %s Seconds until %s *" % ( days_remaining, hours_remaining, mins_remaining, secs_remaining, EVENT_NAME, ) ) # get the length of the packed string display_length = len(countdown_string) # print(display_length) # calculate the amount of time needed to scroll the string scroll_time = display_length * scroll_speed # print(scroll_time) # reset the clock clock = time.monotonic() # scroll the string once display.marquee(countdown_string, scroll_speed, loop=False) # any errors, reset MCU except Exception as e: # pylint: disable=broad-except reset_on_error(10, e)
Upload the Code and Libraries to the QT Py
After downloading the Project Bundle, plug your QT Py ESP32-S2 into the computer's USB port with a known good USB data+power cable. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following folder and file to the QT Py's CIRCUITPY drive.
- /lib
- code.py
Copy the entire lib folder, including all of its contents.
Your QT Py CIRCUITPY drive should resemble the following after copying the lib folder and the code.py file.
Add Your settings.toml File
Remember to add your settings.toml file as described in the Create Your settings.toml File page earlier in the guide. You'll need to include your CIRCUITPY_WIFI_SSID
, CIRCUITPY_WIFI_PASSWORD
, aio_username
and aio_key
in the file.
CIRCUITPY_WIFI_SSID = "your-wifi-ssid-here" CIRCUITPY_WIFI_PASSWORD = "your-wifi-password-here" aio_username = "your-Adafruit-IO-username-here" aio_key = "your-Adafruit-IO-key-here"
How the CircuitPython Code Works
The code begins by assigning a series of variables and a separate single variable. These variables are assigned at the top to enable the user to easily find them in the event they would like to update them.
The series of variables is used to indicate the name, time and date of the event, and the message that will be displayed when the event has started.
EVENT_YEAR = 2022 EVENT_MONTH = 12 EVENT_DAY = 25 EVENT_HOUR = 0 EVENT_MINUTE = 0 EVENT_NAME = "Christmas" EVENT_MSG = "Merry Christmas * "
The separate single variable sets the scroll speed of the text across the displays.
scroll_speed = 0.25
The next section sets up the three displays in series on I2C via the STEMMA QT connector on the QT Py.
The provided I2C addresses assume you did not solder any address jumpers on the back of the first display, soldered A0 on the back of the second display, and soldered A1 on the back of the third display. If you did not do the same, you may need to change the addresses to match.
i2c = board.STEMMA_I2C() display = Seg14x4(i2c, address=(0x70, 0x71, 0x72))
Then you set the brightness to 0.2
out of a range of 0.0 to 1.0, and you print "HELLO WORLD" to the display to give you an indicator of whether everything is wired up and initiated properly.
display.brightness = 0.2 display.print("HELLO WORLD")
Next is the reset_on_error()
function. When the code encounters an error, this function resets the microcontroller after a specified delay. This resets everything, and gives it an opportunity to begin fresh, which can clear up many potential errors.
def reset_on_error(delay, error): print("Error:\n", str(error)) display.print("Error :(") print("Resetting microcontroller in %d seconds" % delay) time.sleep(delay) microcontroller.reset()
Then, the QT Py connects to your WiFi network, followed by Adafruit IO. Both of these connection attempts are wrapped in try
/except
loops so that the code does not get stuck in the event of an error. Rather, the board will reset and try connecting again.
try: wifi.radio.connect(os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")) # any errors, reset MCU except Exception as e: # pylint: disable=broad-except reset_on_error(10, e) aio_username = os.getenv("aio_username") aio_key = os.getenv("aio_key") location = os.getenv("aio_location") pool = socketpool.SocketPool(wifi.radio) requests = adafruit_requests.Session(pool, ssl.create_default_context()) # Initialize an Adafruit IO HTTP API object try: io = IO_HTTP(aio_username, aio_key, requests) except Exception as e: # pylint: disable=broad-except reset_on_error(10, e) print("Connected to Adafruit IO") display.print("Connected IO")
Right before the loop, clock
is setup as a time.monotonic()
device for timekeeping, event_time
packs the event variables into a struct_time()
format and scroll_time
is set to 0
. scroll_time
is used to dynamically adjust the length of time needed to scroll the countdown information across the displays. It's used to loop the countdown as seamlessly as possible.
clock = time.monotonic() event_time = time.struct_time( (EVENT_YEAR, EVENT_MONTH, EVENT_DAY, EVENT_HOUR, EVENT_MINUTE, 0, -1, -1, False) ) scroll_time = 0
In the loop, after scroll_time
has passed, Adafruit IO is pinged to get the current time. Then the current time is subtracted from the event time to find how much time is remaining. This calculation is done by passing event_time
and now
to the mktime()
function, which converts struct_time()
format to seconds. The result of this is stored in remaining
.
if (clock + scroll_time) < time.monotonic(): now = io.receive_time() # print(now) # print(event_time) remaining = time.mktime(event_time) - time.mktime(now)
If remaining
is less than zero, then that means that the day of your event is here and the text you stored in EVENT_MSG
is scrolled across the displays on a loop.
# if it's the day of the event... if remaining < 0: # scroll the event message on a loop display.marquee(EVENT_MSG, scroll_speed, loop=True)
Otherwise, the seconds remaining are calculated back into days, hours, minutes and seconds. These values are packed into the countdown_string
.
# calculate the seconds remaining secs_remaining = remaining % 60 remaining //= 60 # calculate the minutes remaining mins_remaining = remaining % 60 remaining //= 60 # calculate the hours remaining hours_remaining = remaining % 24 remaining //= 24 # calculate the days remaining days_remaining = remaining # pack the calculated times into a string to scroll countdown_string = ( "* %d Days, %d Hours, %d Minutes & %s Seconds until %s *" % ( days_remaining, hours_remaining, mins_remaining, secs_remaining, EVENT_NAME, ) )
The length of the countdown_string
is multiplied by the scroll_speed
variable to calculate scroll_time
. This means that scroll_time
has the length of time needed to scroll each character in the string across the displays.
clock
is reset right before the marquee()
function begins to scroll the text. After the marquee()
scroll finishes, then scroll_time
will have elapsed and the process can begin again. This allows for the displays to continuously loop the countdown information while updating.
# get the length of the packed string display_length = len(countdown_string) # print(display_length) # calculate the amount of time needed to scroll the string scroll_time = display_length * scroll_speed # print(scroll_time) # reset the clock clock = time.monotonic() # scroll the string once display.marquee(countdown_string, scroll_speed, loop=False)
Text editor powered by tinymce.