CircuitPython Day 2024 Countdown Clock
2025-09-19 | By Adafruit Industries
License: See Original Project LCD / TFT
Courtesy of Adafruit
Guide by Tyeth Gundry
Overview
This guide is a demonstration of the new Adafruit Connection Manager library for CircuitPython, along with the new optional time zone argument for the Adafruit IO library.
More Countdown Clocks?
Why on earth might you need another Countdown Clock project? Well maybe you’re planning to remotely trigger another device at the end of the countdown, want the option to count past the event, or maybe because your last countdown clock thought it belonged in another part of the world.
Here at Adafruit, we’ve been offering an easy way for microcontrollers to fetch the current time, via the Time Service integration, as part of Adafruit IO (our IoT platform).
Unfortunately, there have been many cases where the automatic time zone detection has gone haywire, leading to time-based projects not behaving as expected!
Time Zones
Time zones can be tricky, some countries even have a few! That's why when a time zone is not specified, the Adafruit IO Time service uses "automatic time zone detection" based on IP address. This is like having a listing of all the IPs in the world and where they should be located geographically. It's never going to be 100% accurate, as an IP can be shared or recycled or just unlisted. It's estimated to be 99% accurate to the country level.
Instead, the Time Service can accept a requested time zone and respond with exactly the right time for your location/geography. And now the CircuitPython library for Adafruit IO has been updated to add the optional time zone parameter when requesting the time.
Adafruit Connection Manager
The other new kid on the block is the Adafruit Connection Manager library. This library makes getting connected to the internet on different boards a total breeze, by using standardized methods and providing "helpers" for families of hardware (like ESP WiFi or WizNet Ethernet boards) to make programming easier.
For more information, read through this Adafruit-Playground Note which explains it in more detail.
Parts
To complete this project, you will need a microcontroller board supported by CircuitPython, with a built-in display (or an external one, possibly with minor code changes).
This project was originally written for the Adafruit Feather ESP32-S2/S3 Reverse TFT, but then adapted to also run on the Adafruit Qualia (ESP32-S3) with the 3.2" bar display (820 x 320 pixels). To support this there is a conditional block that selects a larger font and background image when running on the Qualia board.
It should also work on most other boards, although you may want to use a differently sized background image to better suit your display's size and aspect ratio. You can easily edit the supplied image with your favourite graphics editing software (or an online service).
CircuitPython displays refresh much more slowly with overlapping images and labels. On the Qualia the background image is positioned just above the label with no overlap. This brings the refresh speed from under one frame per second up to 5-10fps. Not such an issue on smaller resolution displays (less data to send).
Either use a board with built in display:
Or for the Qualia version:
With either the 3.2" Rectangle Bar display (820x320 pixels):
Or the touchscreen version (which has a bezel with nice, rounded corners):
Adafruit IO
As this project uses Adafruit IO you’ll need to have setup an Adafruit account, then logged in at io.adafruit.com.
If you’ve never used Adafruit IO before then it would be wise to read these guides:
Feeds
A feed is a store for data points, you can read more about them in the guides linked above, but for now, it's enough to know we need one of them.
We'll use the feed we create to receive our countdown completion message, and then automatically trigger an Action to signify CircuitPython Day.
Go to the IO Feeds page (io.adafruit.com/feeds/) and use the + New Feed button to create a new feed, giving it the name of cpday-countdown.
Your new feed will appear in the list of My Feeds, the default group at the top of the feeds page. Clicking its name will take you to the individual feed page, showing all previous data points, and a button to add new data (and download all), along with options for configuring the feed (left sidebar or top menu on small screens).
We'll revisit the cpday-countdown feed page later, to test our automatically triggered Action, by manually adding data to the feed.
Actions
On Adafruit IO there are automatic Actions available. You can set up an action to be triggered by a new feed value, or potentially after a delay, or on a set schedule. Then the Action can perform various processing tasks, followed by an output task like publish to a feed or send an email.
You can read more in this guide to the new Blockly Actions:
How to use Blocky for Actions on Adafruit IO
We will use the gets data matching trigger, with the value set to "Launch the Snakes!", so as soon as that message arrives then our action will trigger.
Start by going to the Actions page and creating a new Action, entering a name and optional description.
Now you're presented with the Blockly Action editor page, with a toolbox full of blocks, and the main diagram workspace to the right, with a single Root block designed to receive trigger and action blocks.
Trigger:
Select the When FEED gets data matching = 0 block from the Triggers category in the side panel/toolbox. Drop it into the top Triggers: section of the root block. Then select the cpday-countdown feed from the feed dropdown list.
Now make sure the Operator is set to equals (=) so the action only runs if the new data sent to our feed exactly matches an expected value.
Lastly, you need to get the String Comparison Block from the Triggers category in the toolbox, it is the one with equals speech marks ( = ""). Drag the string comparison block into place where the value block is attached to the trigger, directly on top of the value placeholder block.
Enter the String block by clicking inside and entering the value Launch the snakes!
Action: (Output)
Next the output, for this example you'll send an email, but you could configure anything your mind can come up with, you can even do multiple outputs in the same action.
Maybe the countdown could trigger a real space launch of CircuitPython-powered hardware...Blinka in Space!
Drag the Email block from the Notifications category in the toolbox, dropping it into the Actions: section of the root block, aligning the bumps and dents until the block gains a yellow outline, causing it to snap into place.
Add your custom subject and body to the email, ensuring you get a reminder for CircuitPython Day wherever you are..
Your finished Blockly Action should look very similar to this:
Testing
To test the countdown, launch action you need to go to the cpday-countdown feed page, by navigating to the Feeds page and then clicking on the cpday-countdown feed name.
Use the Add Data button to add the value Launch the snakes!
You should receive an email nearly instantly, to the primary email address registered on your Adafruit account, reminding you of the snakiest day of the year (or whatever message you intended).
IO Secret Key - Required by settings.toml
To get your IO Key (and username) easily, click the View Key option from the menu on small screens, or use the big yellow Key icon for larger screens, from any IO page. You'll see a CircuitPython-compatible set listed, which you'll copy later into your settings.toml file.
Integrations
There are Power-Ups + Integrations on Adafruit IO, like IFTTT/WeatherKit/Zapier/SMS.
The Time service integration is what we’re using in this project to fetch the initial time and account for any time-zone challenges.
See more details at io.adafruit.com/services/time [Must be logged in].
Look up your time-zone from the table linked there, referring to the TZ Identifier column of the table, and make a note of it as it will be used later in the code.
Speaking of code, it’s now time to get CircuitPython set up and take a tour of how the different sections of the code work.
CircuitPython
CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the CIRCUITPY drive to iterate.
CircuitPython Quickstart
Follow this step-by-step to quickly get CircuitPython running on your board.
Download the latest version of CircuitPython for this board via circuitpython.org
Click the link above to download the latest CircuitPython UF2 file.
Save it wherever is convenient for you.
Plug your board into your computer, using a known-good data-sync cable, directly, or via an adapter if needed.
Double-click the reset button (highlighted in red above), and you will see the RGB status LED(s) turn green (highlighted in green above). If you see red, try another port, or if you're using an adapter or hub, try without the hub, or different adapter or hub.
For this board, tap reset and wait for the LED to turn purple, and as soon as it turns purple, tap reset again. The second tap needs to happen while the LED is still purple.
If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!
A lot of people end up using charge-only USB cables and it is very frustrating! Make sure you have a USB cable you know is good for data sync.
You will see a new disk drive appear called FTHRS3BOOT.
Drag the adafruit_circuitpython_etc.uf2 file to FTHRS3BOOT.
The BOOT drive will disappear, and a new disk drive called CIRCUITPY will appear.
That’s it!
Create Your settings.toml File
CircuitPython works with WiFi-capable boards to enable you to make projects that have network connectivity. This means working with various passwords and API keys. As of CircuitPython 8, there is support for a settings.toml file. This is a file that is stored on your CIRCUITPY drive, that contains all of your secret network information, such as your SSID, SSID password and any API keys for IoT services. It is designed to separate your sensitive information from your code.py file so you are able to share your code without sharing your credentials.
CircuitPython previously used a secrets.py file for this purpose. The settings.toml file is quite similar.
Your settings.toml file should be stored in the main directory of your CIRCUITPY drive. It should not be in a folder.
CircuitPython settings.toml File
This section will provide a couple of examples of what your settings.toml file should look like, specifically for CircuitPython WiFi projects in general.
The most minimal settings.toml file must contain your WiFi SSID and password, as that is the minimum required to connect to WiFi. Copy this example, paste it into your settings.toml, and update:
your_wifi_ssid
your_wifi_password
CIRCUITPY_WIFI_SSID = "your_wifi_ssid" CIRCUITPY_WIFI_PASSWORD = "your_wifi_password"
Many CircuitPython network-connected projects on the Adafruit Learn System involve using Adafruit IO. For these projects, you must also include your Adafruit IO username and key. Copy the following example, paste it into your settings.toml file, and update:
your_wifi_ssid
your_wifi_password
your_aio_username
your_aio_key
CIRCUITPY_WIFI_SSID = "your_wifi_ssid" CIRCUITPY_WIFI_PASSWORD = "your_wifi_password" ADAFRUIT_AIO_USERNAME = "your_aio_username" ADAFRUIT_AIO_KEY = "your_aio_key"
Some projects use different variable names for the entries in the settings.toml file. For example, a project might use ADAFRUIT_AIO_ID in the place of ADAFRUIT_AIO_USERNAME. If you run into connectivity issues, one of the first things to check is that the names in the settings.toml file match the names in the code.
Not every project uses the same variable name for each entry in the settings.toml file! Always verify it matches the code.
settings.toml File Tips
Here is an example settings.toml file.
# Comments are supported CIRCUITPY_WIFI_SSID = "guest wifi" CIRCUITPY_WIFI_PASSWORD = "guessable" CIRCUITPY_WEB_API_PORT = 80 CIRCUITPY_WEB_API_PASSWORD = "passw0rd" test_variable = "this is a test" thumbs_up = "\U0001f44d"
In a settings.toml file, it's important to keep these factors in mind:
Strings are wrapped in double quotes; ex: "your-string-here"
Integers are not quoted and may be written in decimal with optional sign (+1, -1, 1000) or hexadecimal (0xabcd).
Floats, octal (0o567) and binary (0b11011) are not supported.
Use \u escapes for weird characters, \x and \ooo escapes are not available in .toml files
Example: \U0001f44d for 👍 (thumbs up emoji) and \u20ac for € (EUR sign)
Unicode emoji, and non-ASCII characters, stand for themselves as long as you're careful to save in "UTF-8 without BOM" format
When your settings.toml file is ready, you can save it in your text editor with the .toml extension.
Accessing Your settings.toml Information in code.py
In your code.py file, you'll need to import the os library to access the settings.toml file. Your settings are accessed with the os.getenv() function. You'll pass your settings entry to the function to import it into the code.py file.
import os print(os.getenv("test_variable"))
In the upcoming CircuitPython WiFi examples, you'll see how the settings.toml file is used for connecting to your SSID and accessing your API keys.
Code the Countdown Clock
Once you've finished setting up your board with CircuitPython, you can access the project code, assets and necessary libraries by downloading the Project Bundle.
To do this, click on the Download Project Bundle button at the top of the code window below.
It will download to your computer as a zipped folder, containing two sets of folders, one each for the current and previous major versions of CircuitPython.
Use the newest version included in the Project Bundle.
# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries # SPDX-FileCopyrightText: 2024 Tyeth Gundry for Adafruit Industries # # SPDX-License-Identifier: MIT import os import time import wifi import board import displayio import supervisor import adafruit_connection_manager import adafruit_requests from adafruit_io.adafruit_io import IO_HTTP from adafruit_bitmap_font import bitmap_font from adafruit_display_text import bitmap_label from adafruit_ticks import ticks_ms, ticks_add, ticks_diff ## See TZ Identifier column at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones ## If you want to set the timezone, you can do so with the following code, which ## attempts to get timezone from settings.toml or defaults to New York timezone = os.getenv("ADAFRUIT_AIO_TIMEZONE", "America/New_York") ## Or instead rely on automatic timezone detection based on IP Address # timezone = None ## The time of the thing! EVENT_YEAR = 2024 EVENT_MONTH = 8 EVENT_DAY = 16 EVENT_HOUR = 0 EVENT_MINUTE = 0 ## we'll make a python-friendly structure event_time = time.struct_time( ( EVENT_YEAR, EVENT_MONTH, EVENT_DAY, EVENT_HOUR, EVENT_MINUTE, 0, # we don't track seconds -1, # we dont know day of week/year or DST -1, False, ) ) print("Connecting to WiFi...") wifi.radio.connect( os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") ) ## Initialize a requests session using the newer connection manager ## See https://adafruit-playground.com/u/justmobilize/pages/adafruit-connection-manager pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) requests = adafruit_requests.Session(pool, ssl_context) ## Create an instance of the Adafruit IO HTTP client io = IO_HTTP( os.getenv("ADAFRUIT_AIO_USERNAME"), os.getenv("ADAFRUIT_AIO_KEY"), requests ) ## Setup display and size appropriate assets if board.board_id == "adafruit_qualia_s3_rgb666": # Display Initialisation for 3.2" Bar display (320x820) from qualia_bar_display_320x820 import setup_display display = setup_display() display.rotation = 90 # Rotate the display BITMAP_FILE = "/circuitpython_day_2024_820x260_16bit.bmp" FONT_FILE = "/font_free_mono_bold_48.pcf" FONT_Y_OFFSET = 30 blinka_bitmap = displayio.OnDiskBitmap(BITMAP_FILE) PIXEL_SHADER = displayio.ColorConverter( input_colorspace=displayio.Colorspace.RGB565 ) else: # Setup built-in display display = board.DISPLAY BITMAP_FILE = "/cpday_tft.bmp" FONT_FILE = "/Helvetica-Bold-16.pcf" FONT_Y_OFFSET = 13 PIXEL_SHADER = displayio.ColorConverter() blinka_bitmap = displayio.OnDiskBitmap(BITMAP_FILE) PIXEL_SHADER = blinka_bitmap.pixel_shader group = displayio.Group() font = bitmap_font.load_font(FONT_FILE) blinka_grid = displayio.TileGrid(blinka_bitmap, pixel_shader=blinka_bitmap.pixel_shader) scrolling_label = bitmap_label.Label(font, text=" ", y=display.height - FONT_Y_OFFSET) group.append(blinka_grid) group.append(scrolling_label) display.root_group = group display.auto_refresh = False refresh_clock = ticks_ms() refresh_timer = 3600 * 1000 # 1 hour clock_clock = ticks_ms() clock_timer = 1000 scroll_clock = ticks_ms() scroll_timer = 50 first_run = True finished = False triggered = False while True: # only query the online time once per hour (and on first run) if ticks_diff(ticks_ms(), refresh_clock) >= refresh_timer or first_run: try: print("Getting time from internet!") now = time.struct_time(io.receive_time(timezone)) print(now) total_seconds = time.mktime(now) refresh_clock = ticks_add(refresh_clock, refresh_timer) except Exception as e: # pylint: disable=broad-except print("Some error occured, retrying via supervisor.reload in 5seconds! -", e) time.sleep(5) # Normally calling microcontroller.reset() would be the way to go, but due to # a bug causing a reset into tinyUF2 bootloader mode we're instead going to # disconnect wifi to ensure fresh connection + use supervisor.reload() wifi.radio.enabled = False supervisor.reload() if ticks_diff(ticks_ms(), clock_clock) >= clock_timer: remaining = time.mktime(event_time) - total_seconds if remaining < 0: # calculate time since event remaining = abs(remaining) secs_remaining = -(remaining % 60) remaining //= 60 mins_remaining = -(remaining % 60) remaining //= 60 hours_remaining = -(remaining % 24) remaining //= 24 days_remaining = -remaining finished = True if not first_run and days_remaining == 0: scrolling_label.text = ( "It's CircuitPython Day 2024! The snakiest day of the year!" ) # Check for the moment of the event to trigger something (a NASA snake launch) if not triggered and ( hours_remaining == 0 and mins_remaining == 0 and secs_remaining <= 1 # Change at/after xx:yy:01 seconds so we've already updated the display ): # send a signal to an adafruit IO feed, where an Action is listening print("Launch the snakes! (sending message to Adafruit IO)") triggered = True io.send_data("cpday-countdown", "Launch the snakes!") else: # calculate time until event secs_remaining = remaining % 60 remaining //= 60 mins_remaining = remaining % 60 remaining //= 60 hours_remaining = remaining % 24 remaining //= 24 days_remaining = remaining if not finished or (finished and days_remaining < 0): # Add 1 to negative days_remaining to count from end of day instead of start if days_remaining < 0: days_remaining += 1 # Update the display with current countdown value scrolling_label.text = ( f"{days_remaining} DAYS, {hours_remaining} HOURS," + f"{mins_remaining} MINUTES & {secs_remaining} SECONDS" ) total_seconds += 1 clock_clock = ticks_add(clock_clock, clock_timer) if ticks_diff(ticks_ms(), scroll_clock) >= scroll_timer: scrolling_label.x -= 1 if scrolling_label.x < -(scrolling_label.width + 5): scrolling_label.x = display.width + 2 display.refresh() scroll_clock = ticks_add(scroll_clock, scroll_timer) first_run = False
Upload the Code and Libraries to the Board
After downloading the Project Bundle, plug your board 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 items to the board's CIRCUITPY drive.
lib folder
code.py
cpday_tft.bmp
Helvetica-Bold-16.pcf
Additionally, if using the Qualia board then copy these files too:
font_free_mono_bold_48.pcf
circuitpython_day_2024_820x260_16bit.bmp
qualia_bar_display_320x820.py
Your board's CIRCUITPY drive should look similar to this after copying the lib folder, image files (.bmp), font files (.pcf), and the two .py circuitpython code files.
Add Your settings.toml File
As of CircuitPython 8.0.0, there is support for Environment Variables. Environment variables are stored in a settings.toml file. Similar to secrets.py, the settings.toml file separates your sensitive information from your main code.py file. Add your settings.toml file as described in the Create Your settings.toml File page earlier in this guide. You'll need to include your CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD, along with your Adafruit IO details (username and key), and optionally a time zone (or edit the code.py file).
CIRCUITPY_WIFI_SSID = "your-ssid-here" CIRCUITPY_WIFI_PASSWORD = "your-ssid-password-here" ADAFRUIT_AIO_USERNAME = "your-adafruit-io-username" ADAFRUIT_AIO_KEY = "your-super-secret-alpha-numeric-key" ADAFRUIT_AIO_TIMEZONE = "GB"
How the CircuitPython Code Works
At the top of the code, you'll edit time zone to reflect your location or alternatively enter it in the settings.toml file. The event time is also set up. In this case, it's August 16, 2024, at midnight.
## See TZ Identifier column at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones ## If you want to set the timezone, you can do so with the following code, which ## attempts to get timezone from settings.toml or defaults to New York timezone = os.getenv("ADAFRUIT_AIO_TIMEZONE", "America/New_York") ## Or instead rely on automatic timezone detection based on IP Address # timezone = None ## The time of the thing! EVENT_YEAR = 2024 EVENT_MONTH = 8 EVENT_DAY = 16 EVENT_HOUR = 0 EVENT_MINUTE = 0 ## we'll make a python-friendly structure event_time = time.struct_time( ( EVENT_YEAR, EVENT_MONTH, EVENT_DAY, EVENT_HOUR, EVENT_MINUTE, 0, # we don't track seconds -1, # we dont know day of week/year or DST -1, False, ) )
WiFi and IO_HTTP
WiFi is setup along with an Adafruit IO instance to represent the HTTP API (IO_HTTP). There is some additional setup of the request's library used by Adafruit IO, handled by the new Adafruit Connection Manager library. The IO_HTTP class has a method to receive_time and will take care of the timing for this project. Your timezone is passed to the receive_time request to reflect the time in your location.
print("Connecting to WiFi...") wifi.radio.connect( os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD") ) ## Initialize a requests session using the newer connection manager ## See https://adafruit-playground.com/u/justmobilize/pages/adafruit-connection-manager pool = adafruit_connection_manager.get_radio_socketpool(wifi.radio) ssl_context = adafruit_connection_manager.get_radio_ssl_context(wifi.radio) requests = adafruit_requests.Session(pool, ssl_context) ## Create an instance of the Adafruit IO HTTP client io = IO_HTTP( os.getenv("ADAFRUIT_AIO_USERNAME"), os.getenv("ADAFRUIT_AIO_KEY"), requests )
Graphics
Next are the display objects for the external display attached to the Qualia, which calls out to a second file to handle the external display setup, or for any board with a built-in display. This takes care of the background bitmap graphic, font, and text element.
## Setup display and size appropriate assets if board.board_id == "adafruit_qualia_s3_rgb666": # Display Initialisation for 3.2" Bar display (320x820) from qualia_bar_display_320x820 import setup_display display = setup_display() display.rotation = 90 # Rotate the display BITMAP_FILE = "/circuitpython_day_2024_820x260_16bit.bmp" FONT_FILE = "/font_free_mono_bold_48.pcf" FONT_Y_OFFSET = 30 blinka_bitmap = displayio.OnDiskBitmap(BITMAP_FILE) PIXEL_SHADER = displayio.ColorConverter( input_colorspace=displayio.Colorspace.RGB565 ) else: # Setup built-in display display = board.DISPLAY BITMAP_FILE = "/cpday_tft.bmp" FONT_FILE = "/Helvetica-Bold-16.pcf" FONT_Y_OFFSET = 13 PIXEL_SHADER = displayio.ColorConverter() blinka_bitmap = displayio.OnDiskBitmap(BITMAP_FILE) PIXEL_SHADER = blinka_bitmap.pixel_shader group = displayio.Group() font = bitmap_font.load_font(FONT_FILE) blinka_grid = displayio.TileGrid(blinka_bitmap, pixel_shader=blinka_bitmap.pixel_shader) scrolling_label = bitmap_label.Label(font, text=" ", y=display.height - FONT_Y_OFFSET) group.append(blinka_grid) group.append(scrolling_label) display.root_group = group display.auto_refresh = False
Time is Ticking
Finally, three separate ticks timers are created for timekeeping in the loop, along with some variables to hold our state. One boolean variable for if it's the first iteration through the loop (first_run), another for if the event has occurred (finished), and the last one to say if we have sent a message to Adafruit IO to signify the start of the event (triggered).
refresh_clock = ticks_ms() refresh_timer = 3600 * 1000 # 1 hour clock_clock = ticks_ms() clock_timer = 1000 scroll_clock = ticks_ms() scroll_timer = 50 first_run = True finished = False triggered = False
The Loop
In the loop, the time is fetched from the Adafruit IO Time service every hour and stored in now. now is converted to seconds using time.mktime(now). This lets you calculate how much time is remaining until the event.
# only query the online time once per hour (and on first run) if ticks_diff(ticks_ms(), refresh_clock) >= refresh_timer or first_run: try: print("Getting time from internet!") now = time.struct_time(io.receive_time(timezone)) print(now) total_seconds = time.mktime(now) refresh_clock = ticks_add(refresh_clock, refresh_timer) except Exception as e: # pylint: disable=broad-except print("Some error occured, retrying via reset in 15seconds! -", e) time.sleep(15) microcontroller.reset()
The time is kept by the microcontroller in between polling the Time service. Every second, 1 second is added to the total_seconds value tracking the current time. remaining stores the total seconds remaining until, or since, the event. This is converted to days, hours, minutes, and seconds. These values are added to the scrolling text on the display.
When dealing with time after the event (from the beginning of August 16th at midnight), the next 24 hours are still inside the event day (CircuitPython Day) and so checking days_remaining is zero (and triggered is False) allows us to detect when the trigger should be sent to IO (once) during that time.
Then after the event day the remaining time count will list incorrect values as the event is scheduled for the start of a day (midnight), so as long as the event has passed an offset of 1 day is required. The segments of time will also be positive numbers which feels wrong when talking about a past event so they are altered to be negative values.
if ticks_diff(ticks_ms(), clock_clock) >= clock_timer: remaining = time.mktime(event_time) - total_seconds if remaining < 0: # calculate time since event remaining = abs(remaining) secs_remaining = -(remaining % 60) remaining //= 60 mins_remaining = -(remaining % 60) remaining //= 60 hours_remaining = -(remaining % 24) remaining //= 24 days_remaining = -remaining finished = True if not first_run and days_remaining == 0: scrolling_label.text = ( "It's CircuitPython Day 2024! The snakiest day of the year!" ) # Check for the moment of the event to trigger something (a NASA snake launch) if not triggered and ( hours_remaining == 0 and mins_remaining == 0 and secs_remaining <= 1 # Change at/after xx:yy:01 seconds so we've already updated the display ): # send a signal to an adafruit IO feed, where an Action is listening print("Launch the snakes! (sending message to Adafruit IO)") triggered = True io.send_data("cpday-countdown", "Launch the snakes!") else: # calculate time until event secs_remaining = remaining % 60 remaining //= 60 mins_remaining = remaining % 60 remaining //= 60 hours_remaining = remaining % 24 remaining //= 24 days_remaining = remaining if not finished or (finished and days_remaining < 0): # Add 1 to negative days_remaining to count from end of day instead of start if days_remaining < 0: days_remaining += 1 # Update the display with current countdown value scrolling_label.text = ( f"{days_remaining} DAYS, {hours_remaining} HOURS," + f"{mins_remaining} MINUTES & {secs_remaining} SECONDS" ) total_seconds += 1 clock_clock = ticks_add(clock_clock, clock_timer)
The last timer is used to scroll the text by moving the x coordinate of the text by 2 pixels. When the text is offscreen, its x coordinate is reset to start scrolling across again.
At the end of the loop the state variable for first_run is also updated to False.
if ticks_diff(ticks_ms(), scroll_clock) >= scroll_timer: scrolling_label.x -= 1 if scrolling_label.x < -(scrolling_label.width + 5): scrolling_label.x = display.width + 2 display.refresh() scroll_clock = ticks_add(scroll_clock, scroll_timer) first_run = False
Finally, the project will probably survive a bit longer if it's enclosed. The packaging from Adafruit shipments makes for a reasonable project display box with one hole cut for the display.
That's it!