Overview
Every year for Hanukkah, you light a candle every night for 8 days. Often about halfway through the holiday though, you may find yourself losing count of the nights. You may also live in a space where you can't light a candle, like a dorm room or an apartment. This project helps you with all of these problems by making the menorah digital and keeping time with the internet.
Every day, the MagTag fetches the date and time, compares it to the start date of Hanukkah and updates the menorah graphic accordingly. It's also a low power project, taking advantage of deep sleep on the ESP32-S2, so you can keep it battery powered on your desk throughout the season.
The menorah graphic was designed by Noe Ruiz. Each flame on the candles is covered by a circle in the CircuitPython code. As the candles are "lit", the circles are made transparent to reveal the flames.
3D Printed Case
You can 3D print or purchase the Adafruit MagTag case from the shop. The case features a kick stand and space for a battery inside.
Page last edited December 18, 2025
Text editor powered by tinymce.
Install 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.
Click the link above and download the latest .BIN and .UF2 file
You can use a 9.x.x release for a pre-2025 MagTag. You must use a 10.x.x release for the updated MagTag 2025 Edition.
(depending on how you program the ESP32S2 board you may need one or the other, might as well get both)
Download and save it to your desktop (or wherever is handy).
Plug your MagTag into your computer using a known-good USB cable.
A lot of people end up using charge-only USB cables and it is very frustrating! So make sure you have a USB cable you know is good for data sync.
Option 1 - Load with UF2 Bootloader
This is by far the easiest way to load CircuitPython. However it requires your board has the UF2 bootloader installed. Some early boards do not (we hadn't written UF2 yet!) - in which case you can load using the built in ROM bootloader.
Still, try this first!
Try Launching UF2 Bootloader
Loading CircuitPython by drag-n-drop UF2 bootloader is the easier way and we recommend it. If you have a MagTag where the front of the board is black, your MagTag came with UF2 already on it.
Launch UF2 by double-clicking the Reset button (the one next to the USB C port). You may have to try a few times to get the timing right.
If you're using Windows and you get an error at the end of the file copy that says Error from the file copy, Error 0x800701B1: A device which does not exist was specified. You can ignore this error, the bootloader sometimes disconnects without telling Windows, the install completed just fine and you can continue. If its really annoying, you can also upgrade the bootloader (the latest version of the UF2 bootloader fixes this warning)
Your board should auto-reset into CircuitPython, or you may need to press reset. A CIRCUITPY drive will appear. You're done! Go to the next pages.
Option 2 - Use esptool to load BIN file
If you have an original MagTag with while soldermask on the front, we didn't have UF2 written for the ESP32S2 yet so it will not come with the UF2 bootloader.
You can upload with esptool to the ROM (hardware) bootloader instead!
Follow the initial steps found in the Run esptool and check connection section of the ROM Bootloader page to verify your environment is set up, your board is successfully connected, and which port it's using.
In the final command to write a binary file to the board, replace the port with your port, and replace "firmware.bin" with the the file you downloaded above.
The output should look something like the output in the image.
Press reset to exit the bootloader.
Your CIRCUITPY drive should appear!
You're all set! Go to the next pages.
Option 3 - Use Chrome Browser To Upload BIN file
If for some reason you cannot get esptool to run, you can always try using the Chrome-browser version of esptool we have written. This is handy if you don't have Python on your computer, or something is really weird with your setup that makes esptool not run (which happens sometimes and isn't worth debugging!) You can follow along on the Web Serial ESPTool page and either load the UF2 bootloader and then come back to Option 1 on this page, or you can download the CircuitPython BIN file directly using the tool in the same manner as the bootloader.
Page last edited December 18, 2025
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 December 18, 2025
Text editor powered by tinymce.
Code the Menorah
Once you've finished setting up your MagTag 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: 2025 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""MagTag IoT Menorah"""
import time
import displayio
from adafruit_magtag.magtag import MagTag
from adafruit_display_shapes.circle import Circle
# latitude
lat = 42.36
# longitude
long = -71.06
# timezone offset from GMT
tz_offset = -5
hanukkah_date = [12, 14] # month, date
# open meteo API for sunset, today and tomorrow
sunset_fetch = (f"https://api.open-meteo.com/v1/forecast?"
f"latitude={lat}&longitude={long}&daily=sunset"
f"&timezone=auto&forecast_days=2&timeformat=unixtime")
today_sunset = ["daily", "sunset", 0]
tomorrow_sunset = ["daily", "sunset", 1]
# create MagTag and connect to network
try:
magtag = MagTag(
url=sunset_fetch,
json_path=(today_sunset, tomorrow_sunset),
default_bg=0x000000,
)
magtag.network.connect()
except (ConnectionError, ValueError, RuntimeError) as e:
print("*** MagTag(), Some error occured, retrying! -", e)
# Exit program and restart in 1 seconds.
magtag.exit_and_deep_sleep(1)
# displayio groups
group = displayio.Group()
menorah_group = displayio.Group()
circle_group = displayio.Group()
# import menorah bitmap
filename = "/magtag_menorah.bmp"
menorah = displayio.OnDiskBitmap(filename)
menorah_grid = displayio.TileGrid(menorah, pixel_shader=menorah.pixel_shader)
# add bitmap to its group
menorah_group.append(menorah_grid)
# add menorah group to the main group
group.append(menorah_group)
# list of circle positions
spots = (
(148, 16), # shamash
(272, 31), # 1st
(242, 31), # 2nd
(212, 31), # 3rd
(182, 31), # 4th
(114, 31), # 5th
(84, 31), # 6th
(54, 31), # 7th
(24, 31), # 8th
)
# creating the circles & pulling in positions from spots
for spot in spots:
circle = Circle(x0=spot[0], y0=spot[1], r=13, fill=0xFFFFFF)
# adding circles to their display group
circle_group.append(circle)
# adding circles group to main display group
group.append(circle_group)
# grabs time from network
magtag.get_local_time()
# parses time into month, date, etc
now = time.localtime()
print(f"now is {now}")
month = now[1]
day = now[2]
day_count = 0
seconds_to_sleep = 3600
# check if its hanukkah
if month == hanukkah_date[0]:
# get the night count for hanukkah
if hanukkah_date[1] <= day <= hanukkah_date[1] + 8:
day_count = (day - hanukkah_date[1]) + 1
print(f"it's the {day_count} night of hanukkah!")
elif day > hanukkah_date[1] + 8:
day_count = 8
unix_now = time.mktime(now)
# adjust unixtime to your timezone (otherwise in GMT-0)
unix_now = unix_now + -(tz_offset*3600)
print(unix_now)
sunsets = magtag.fetch()
if unix_now < sunsets[0]:
seconds_to_sleep = sunsets[0] - unix_now
# don't light the next candle until sunset
if 0 < day_count < 8:
day_count -= 1
print("the sun is still up")
else:
seconds_to_sleep = sunsets[1] - unix_now
if day_count > 0:
# sets colors of circles to transparent to reveal flames
for i in range(day_count + 1):
circle_group[i].fill = None
time.sleep(0.1)
# updates display with bitmap and current candles
magtag.display.root_group = group
time.sleep(5)
magtag.display.refresh()
time.sleep(5)
# goes into deep sleep till next sunset
print("entering deep sleep")
print(f"sleeping for {seconds_to_sleep} seconds")
magtag.exit_and_deep_sleep(seconds_to_sleep)
# entire code will run again after deep sleep cycle
# similar to hitting the reset button
Upload the Code and Libraries to the MagTag
After downloading the Project Bundle, plug your MagTag 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 MagTag's CIRCUITPY drive.
- lib folder
- code.py
- magtag_menorah.bmp
Your MagTag CIRCUITPY drive should look like this after copying the lib folder, bitmap image file 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 values for your CIRCUITPY_WIFI_SSID and CIRCUITPY_WIFI_PASSWORD.
CIRCUITPY_WIFI_SSID = "your-ssid-here" CIRCUITPY_WIFI_PASSWORD = "your-ssid-password-here"
Page last edited December 18, 2025
Text editor powered by tinymce.
Assembly
Install Buttons
Hold the top half of the enclosure face side down and place the four buttons into the holes. Then set it aside.
Install Battery (Optional)
Connect the optional 500mAh lipo battery to the MagTag. Use a piece of double-sided tap or mounting tack to keep the battery adhered to the MagTag's PCB.
Install MagTag
Get the bottom half of the enclosure and four M3 x 6mm long nylon screws.
Orient the MagTag with the bottom half of the enclosure. Place the MagTag into the bottom with the mounting holes lined up.
Secure MagTag
Flip the MagTag and bottom part and fasten the M3 screws into the four mounting holes.
Install Switch
Place the switch into the slot near the MagTag's on/off switch. The actuator of the on/off switch should fit in between the two nubs.
Join Top and Bottom
Orient the top and bottom half of the enclosure parts so they orient. While holding the switch in place, begin joining the two halves together making sure the four buttons stay in place.
Firmly join the two halves together until they snap fit closed.
Kickstand
To use the kickstand, locate the notch on the back side. To pop out the stand, fit your fingernail under the ridge of the stand and pull it out.
The stand can be stowed away by snaping it into the back side of the enclosure.
Assembled Case
The assembled case is ready to prop on your desk, or stick to your refrigerator or other metal surface using the magnetic feet.
Page last edited December 18, 2025
Text editor powered by tinymce.
Use
At the top of the code are a few user parameters you'll want to update. lat and long are for your latitude and longitude location. This is used with the Open-Meteo API to fetch the sunset time for your location. tz_offset is your GMT timezone offset as an integer (examples: -5 for New York, 9 for Tokyo, Japan). Finally, hanukkah_date carries the month and date for the first day of Hanukkah.
# latitude lat = 42.36 # longitude long = -71.06 # timezone offset from GMT tz_offset = -5 hanukkah_date = [12, 14] # month, date
After updating these values, you can run the code on your MagTag. The project utilizes deep sleep, so you can power it with a battery.
On wake, the MagTag fetches the date and time from the internet. It checks if the date is coinciding with Hanukkah based on the date at the top of the code. This determines how many candles will be lit. The sunset time is fetched and compared to the current time. If it's after sunset, the next candle is lit. Then, the MagTag goes back into deep sleep until sunset the next day.
Page last edited December 18, 2025
Text editor powered by tinymce.