Overview
Countdown the days until CircuitPython Day 2024! This project uses a QT Py ESP32-S2 and NeoPixel dots strand to light up each day, counting down the days until August 16th 2024 - The snakiest day of the year!
The CircuitPython code uses Adafruit IO for time keeping. A single NeoPixel LED will light up each day and all LEDs are lit on August 16, CircuitPython Day 2024.
3D print an LED sign in the shape of Blinka, the official CircuitPython mascot. Each NeoPixel LED is fitted behind a little 3D printed bulb to create 16 lights, representing each day of August.
This special Ouroboros depicts Blinka biting her own tail to represent the cycle of CircuitPython development.
Page last edited September 06, 2024
Text editor powered by tinymce.
Circuit Diagram
The diagram below provides a general visual reference for wiring of the components once you get to the Assembly page. This diagram was created using the software package Fritzing.
Adafruit Library for Fritzing
Adafruit uses the Adafruit Fritzing parts library to create circuit diagrams for projects. You can download the library or just grab individual parts. Get the library and parts from GitHub - Adafruit Fritzing Parts.
Page last edited September 06, 2024
Text editor powered by tinymce.
CAD Files
3D Printed Parts
STL files for 3D printing are oriented to print "as-is" on FDM style machines. Parts are designed to 3D print without any support material using PLA filament. Original design source may be downloaded using the links below.
Build Volume
The parts require a 3D printer with a minimum build volume.
- 184mm (X) x 199mm (Y) x 12mm (Z)
Design Source Files
The project assembly was designed in Fusion 360. This can be downloaded in different formats like STEP, STL and more.
Electronic components like Adafruit's boards, displays, connectors and more can be downloaded from the Adafruit CAD parts GitHub Repo.
Page last edited September 06, 2024
Text editor powered by tinymce.
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.
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.
Click the reset button once (highlighted in red above), and then click it again when you see the RGB status LED(s) (highlighted in green above) turn purple (approximately half a second later). Sometimes it helps to think of it as a "slow double-click" of the reset button.
If you do not see the LED turning purple, you will need to reinstall the UF2 bootloader. See the Factory Reset page in this guide for details.
On some very old versions of the UF2 bootloader, the status LED turns red instead of purple.
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.
Once successful, you will see the RGB status LED(s) turn green (highlighted in green above), and a disk drive ending in "...BOOT" should appear on your host computer. 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.
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 QTPYS2BOOT.
Drag the adafruit_circuitpython_etc.uf2 file to QTPYS2BOOT.
Copy or drag the UF2 file you downloaded to the BOOT drive.
Page last edited September 06, 2024
Text editor powered by tinymce.
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.
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_ssidyour_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_ssidyour_wifi_passwordyour_aio_usernameyour_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.
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 (decimal numbers), octal (
0o567) and binary (0b11011) are not supported.
- Floats (decimal numbers), octal (
- Use
\uescapes for weird characters,\xand\oooescapes are not available in .toml files- Example:
\U0001f44dfor 👍 (thumbs up emoji) and\u20acfor € (EUR sign)
- Example:
- 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.
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.
Page last edited September 06, 2024
Text editor powered by tinymce.
Code the Sign
Once you've finished setting up your QT Py ESP32-S2 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 to your computer as a zipped folder.
# SPDX-FileCopyrightText: 2024 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import os
import time
import wifi
import microcontroller
import board
import neopixel
import adafruit_connection_manager
import adafruit_requests
from adafruit_io.adafruit_io import IO_HTTP
from adafruit_ticks import ticks_ms, ticks_add, ticks_diff
timezone = "America/New_York"
color = 0xFF00FF
# 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, -1, False)) # we dont know day of week/year or DST
print("Connecting to WiFi...")
wifi.radio.connect(
os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")
)
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)
io = IO_HTTP(
os.getenv("AIO_USERNAME"), os.getenv("AIO_KEY"), requests
)
pixel_pin = board.SCL1
pixel_num = 16
pixels = neopixel.NeoPixel(pixel_pin, n = pixel_num, brightness=1, auto_write=True)
pixel_length = 0
last_length = -1
refresh_clock = ticks_ms()
refresh_timer = 3600 * 1000 # 1 hour
first_run = True
finished = False
while True:
if not finished:
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)
remaining = time.mktime(event_time) - total_seconds
if remaining < 0:
pixel_length = pixel_num + 1
finished = True
else:
if now.tm_mon == EVENT_MONTH:
pixel_length = now.tm_mday % (pixel_num + 1)
refresh_clock = ticks_add(refresh_clock, refresh_timer)
except Exception as e: # pylint: disable=broad-except
print("Some error occured, retrying via reset in 15 seconds! - ", e)
time.sleep(15)
microcontroller.reset()
if last_length != pixel_length:
if not pixel_length:
pixels.fill(0x000000)
else:
for i in range(pixel_length):
pixels[i] = color
last_length = pixel_length
first_run = False
Upload the Code and Libraries to the QT Py ESP32-S2
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 items to the QT Py ESP32-S2's CIRCUITPY drive.
- lib folder
- code.py
Your QT Py ESP32-S2 CIRCUITPY drive should look like this after copying the lib folder and the code.py file.
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 AIO_USERNAME, AIO_KEY, CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD.
CIRCUITPY_WIFI_SSID = "your-ssid-here" CIRCUITPY_WIFI_PASSWORD = "your-ssid-password-here" AIO_USERNAME = "your-username-here" AIO_KEY = "your-key-here"
How the CircuitPython Code Works
At the top of the code, you'll edit timezone to reflect your timezone location. You can edit color to change the color of the NeoPixels. The event time is also set. In this case, it's August 16, 2024 at midnight.
timezone = "America/New_York"
color = 0xFF00FF
# 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, -1, False)) # we dont know day of week/year or DST
WiFi
The board connects to WiFi and then establishes a connection with Adafruit IO. Adafruit IO is used as a time server for this project.
print("Connecting to WiFi...")
wifi.radio.connect(
os.getenv("CIRCUITPY_WIFI_SSID"), os.getenv("CIRCUITPY_WIFI_PASSWORD")
)
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)
io = IO_HTTP(
os.getenv("AIO_USERNAME"), os.getenv("AIO_KEY"), requests
)
NeoPixels
The NeoPixels are setup on pin SCL1 for the data connection. This is not the standard use for this STEMMA I2C pin, but using it makes it a solderless project. pixel_length and last_length are used in the loop to keep track of which NeoPixels are lit for the countdown.
pixel_pin = board.SCL1 pixel_num = 16 pixels = neopixel.NeoPixel(pixel_pin, n = pixel_num, brightness=1, auto_write=True) pixel_length = 0 last_length = -1
Time and States
adafruit_ticks is used for timekeeping in the loop. first_run and finished are used as states for tracking the state of the timekeeping.
refresh_clock = ticks_ms() refresh_timer = 3600 * 1000 # 1 hour first_run = True finished = False
The Loop
In the loop, the time is checked every hour via Adafruit IO. Since the NeoPixels are representing days until an event, the time does not need to be checked as often as with other countdown projects. The date is used as the count for the NeoPixels. For example, if it's the 6th of the event month, then 6 NeoPixels will be lit. If the countdown is over, then all of the NeoPixels are lit.
while True:
if not finished:
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)
remaining = time.mktime(event_time) - total_seconds
if remaining < 0:
pixel_length = pixel_num + 1
finished = True
else:
if now.tm_mon == EVENT_MONTH:
pixel_length = now.tm_mday % (pixel_num + 1)
refresh_clock = ticks_add(refresh_clock, refresh_timer)
except Exception as e: # pylint: disable=broad-except
print("Some error occured, retrying via reset in 15 seconds! - ", e)
time.sleep(15)
microcontroller.reset()
if last_length != pixel_length:
if not pixel_length:
pixels.fill(0x000000)
else:
for i in range(pixel_length):
pixels[i] = color
last_length = pixel_length
first_run = False
Page last edited September 06, 2024
Text editor powered by tinymce.
Assembly
NeoPixel Dot Strand
Take a moment to review the input and output connectors on the NeoPixel dot strand.
Locate the Data IN connector and use this to connect the STEMMA QT cable.
Connect STEMMA QT
Reference the image for connecting the STEMMA QT cable to the Data In connector on the NeoPixel strand.
Test NeoPixel Strand
Connect the STEMMA QT cable to the QT Py and use a 5V 1A power supply or computer USB port to power the QT Py.
NeoPixel Strip Placement
Locate the first NeoPixel on the strip and place it near the hole closest to Blinka's head and eye.
Position the STEMMA QT cable so it's fitted through the slit near the bottom of the sign.
Install NeoPixels
Hot glue all 16 NeoPixel LEDs to the holes in the 3D printed sign.
Allow each LED to cool down before proceeding to the next LED.
Test the NeoPixel strip once all of the LEDs have been dried.
Install QT Py to Holder
Insert the QT Py into the 3D printed holder at an angle so the edge of the PCB is fitted under the corner clips.
Slightly flex the holder to allow the corner clips on the opposite side to clamp the QT Py into place.
Install QT Py to Bottom Cover
Use two M3x6mm long screws and hex nuts to secure the 3D printed QT Py holder to the 3D printed bottom cover.
Place the QT Py holder over the 3D printed bottom cover with the mounting holes lined up.
Secure QT Py Holder
Insert and fasten the M3 x 6mm long machine screws. Use the M3 hex nuts to secure the QT Py holder to the bottom cover.
Base Stand
Use two M3 x 6mm long machine screws and hex nuts to secure the base stand to the 3D printed Blinka sign.
Secure Base to Sign
Insert and fasten the M3 machine screws through the two mounting holes on the inside of the base stand.
Use the M3 hex nuts to secure the base to the sign.
Fit STEMMA QT Cable
Insert the STEMMA QT cable through the hole cutout in the 3D printed base and pull it all the way through.
Connect STEMMA QT Cable
Plug in the STEMMA QT cable into the STEMMA QT port on the QT Py.
Ensure the cable is fully seated into the port on the QT Py.
Install Bottom Cover
Line up the bottom cover with the base so the USB port on QT Py is lined up with the cutout.
Firmly press the bottom cover into the base to close them shut.
Final Build
Use a 5V 1A power supply or computers USB port to power on the Blinka NeoPixel LED sign.
Congrats on your build!
Page last edited September 06, 2024
Text editor powered by tinymce.