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: 2023 Liz Clark for Adafruit Industries # SPDX-License-Identifier: MIT import os import ssl import time import microcontroller import board import wifi import socketpool import adafruit_requests import neopixel import simpleio from adafruit_ticks import ticks_ms, ticks_add, ticks_diff from adafruit_io.adafruit_io import IO_HTTP # latitude lat = 42.36 # longitude long = -71.06 # neopixel setup NUMPIXELS = 30 # number of neopixels BRIGHTNESS = 0.5 # A number between 0.0 and 1.0, where 0.0 is off, and 1.0 is max. PIN = board.A3 # This is the default pin on the NeoPixel Driver BFF. pixels = neopixel.NeoPixel(PIN, NUMPIXELS, brightness=BRIGHTNESS, auto_write=False) # turn on NeoPixels on boot to check wiring pixels.fill((255, 125, 0)) pixels.show() # API request to open-meteo weather_url = "https://api.open-meteo.com/v1/forecast?" # pass latitude and longitude # will return sunrise and sunset times weather_url += "latitude=%d&longitude=%d&timezone=auto&daily=sunrise,sunset" % (lat, long) # connect to SSID wifi.radio.connect(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD')) pool = socketpool.SocketPool(wifi.radio) requests = adafruit_requests.Session(pool, ssl.create_default_context()) pool = socketpool.SocketPool(wifi.radio) # adafruit IO info aio_username = os.getenv('aio_username') aio_key = os.getenv('aio_key') location = "America/New York" # io HTTP for getting the time from the internet io = IO_HTTP(aio_username, aio_key, requests) def reset_on_error(delay, error): print("Error:\n", str(error)) print("Resetting microcontroller in %d seconds" % delay) time.sleep(delay) microcontroller.reset() # function for making http requests with try/except def get_request(tries, ping): for i in range(tries): try: n = ping except Exception as error: print(error) time.sleep(10) if i < tries - 1: continue raise break return n # get the time on start-up # pylint: disable=broad-except try: now = get_request(5, io.receive_time()) except Exception as e: reset_on_error(10, e) print(now) today = now.tm_mday # function to make a request to open-meteo def sun_clock(): # make the API request response = get_request(5, requests.get(weather_url)) # packs the response into a JSON response_as_json = response.json() # gets sunrise _rise = response_as_json['daily']['sunrise'][0] # gets sunset _set = response_as_json['daily']['sunset'][0] return _rise, _set # initial API call try: sunrise, sunset = sun_clock() except Exception as e: reset_on_error(10, e) print(sunrise) print(sunset) # the sunrise/sunset time is returned as a JSON aka a string # this function chops up the string to get the hours and minutes as integers def divide_time(z): string_time = z.split("-") clock_time = string_time[2].split("T") int_time = clock_time[1].split(":") event_time = time.struct_time( (int(string_time[0]), int(string_time[1]), int(clock_time[0]), int(int_time[0]), int(int_time[1]), 0, -1, -1, False) ) # print(event_time) return event_time rise_time = divide_time(sunrise) set_time = divide_time(sunset) # function that tracks how many hours/minutes until sunrise or sunset def sun_countdown(sun_event): n = get_request(5, io.receive_time()) remaining = time.mktime(sun_event) - time.mktime(n) r = remaining # print(remaining) # calculate the seconds remaining secs_remaining = remaining % 60 # pylint: disable=unused-variable remaining //= 60 # calculate the minutes remaining minutes_until = remaining % 60 remaining //= 60 # calculate the hours remaining hours_until = remaining % 24 remaining //= 24 return r, hours_until, minutes_until, n try: total_until_rise, hours_until_sunrise, mins_until_sunrise, now = sun_countdown(rise_time) except Exception as e: reset_on_error(10, e) try: total_until_set, hours_until_sunset, mins_until_sunset, now = sun_countdown(set_time) except Exception as e: reset_on_error(10, e) # red and yellow color percentage for neopixels percent_red = 0 percent_yellow = 0 print(total_until_set) # check to see if the star fragment should be lit up on start-up if total_until_set < 0: print("star glow true") star_glow = True percent_red = 255 percent_yellow = 125 # turn neopixels on using RGB values pixels.fill((percent_red, percent_yellow, 0)) pixels.show() else: print("star glow false") star_glow = False percent_red = 0 percent_yellow = 0 # turn neopixels on using RGB values pixels.fill((percent_red, percent_yellow, 0)) pixels.show() # ticks time tracker clock = ticks_ms() # tracker for initial start-up state first_run = True # 15 minutes in milliseconds time_check = 900000 # state to tell if it's after midnight yet before sunrise looking_for_sunrise = False while True: try: # if it's daytime if not star_glow: # every 15 minutes... if first_run or ticks_diff(ticks_ms(), clock) > time_check: print("pinging Open-Meteo") sunrise, sunset = sun_clock() (total_until_set, hours_until_sunset, mins_until_sunset, now) = sun_countdown(set_time) print(now) print("%d hour(s) until sunset" % hours_until_sunset) print("%d minutes(s) until sunset" % mins_until_sunset) print(sunset) print(percent_red) print() # less than an hour until sunset... if hours_until_sunset in (0, 23): # check every minute time_check = 300000 # map color to ramp up in brightness over the course of the final hour percent_red = simpleio.map_range(mins_until_sunset, 59, 0, 0, 255) percent_yellow = simpleio.map_range(mins_until_sunset, 59, 0, 0, 125) # if the sun has set.. if total_until_set < 0: percent_red = 255 percent_yellow = 125 time_check = 900000 star_glow = True print("star is glowing") # otherwise just keep checking every 15 minutes else: time_check = 900000 percent_red = 0 percent_yellow = 0 if first_run: first_run = False else: # reset clock clock = ticks_add(clock, time_check) # if it's nighttime... else: if first_run or ticks_diff(ticks_ms(), clock) > time_check: if today != now.tm_mday or (first_run and now.tm_hour < rise_time.tm_hour): today = now.tm_mday looking_for_sunrise = True # begin tracking the incoming sunrise if looking_for_sunrise: print("pinging Open-Meteo") sunrise, sunset = sun_clock() (total_until_rise, hours_until_sunrise, mins_until_sunrise, now) = sun_countdown(rise_time) print(now) print("%d hour(s) until sunrise" % hours_until_sunrise) print("%d minutes(s) until sunrise" % mins_until_sunrise) print(sunrise) print(now) print() # less than an hour until sunset... if hours_until_sunrise in (0, 23): # check every minute time_check = 300000 # map color to decrease brightness over the course of the final hour percent_red = simpleio.map_range(mins_until_sunrise, 59, 0, 255, 0) percent_yellow = simpleio.map_range(mins_until_sunrise, 59, 0, 125, 0) # if the sun has risen.. if total_until_rise < 0: percent_red = 0 percent_yellow = 0 time_check = 900000 star_glow = False looking_for_sunrise = False print("star is off") # otherwise just keep checking every 15 minutes # and keep neopixels on else: time_check = 900000 percent_red = 255 percent_yellow = 125 # otherwise just keep checking every 15 minutes # and keep neopixels on else: now = get_request(5, io.receive_time()) print("not looking for sunrise") print(now) print() time_check = 900000 percent_red = 255 percent_yellow = 125 if first_run: first_run = False else: # reset clock clock = ticks_add(clock, time_check) # turn neopixels on using RGB values pixels.fill((percent_red, percent_yellow, 0)) pixels.show() except Exception as e: reset_on_error(10, e)
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, there is support for Environment Variables. These Environmental 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
, 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"
The Open-Meteo weather API uses latitude and longitude to determine your location when creating an API request. At the top of the code, you can add your latitude and longitude coordinates by editing the lat
and long
variables.
# latitude lat = 42.36 # longitude long = -71.06
weather_url
is a string that holds the Open-Meteo API request. It passes the latitude and longitude variables and requests the sunrise and sunset times.
# API request to open-meteo weather_url = "https://api.open-meteo.com/v1/forecast?" # pass latitude and longitude # will return sunrise and sunset times weather_url += "latitude=%d&longitude=%d&timezone=auto&daily=sunrise,sunset" % (lat, long)
Internet Connect!
The QT Py ESP32-S2 connects to your network by passing your SSID name and SSID password information from the settings.toml file. io
is instantiated as an Adafruit IO HTTP object by passing your IO username and password from the settings.toml file as well. Adafruit IO is used to get the current time.
# connect to SSID wifi.radio.connect(os.getenv('CIRCUITPY_WIFI_SSID'), os.getenv('CIRCUITPY_WIFI_PASSWORD')) pool = socketpool.SocketPool(wifi.radio) requests = adafruit_requests.Session(pool, ssl.create_default_context()) pool = socketpool.SocketPool(wifi.radio) # adafruit IO info aio_username = os.getenv('aio_username') aio_key = os.getenv('aio_key') location = "America/New York" # io HTTP for getting the time from the internet io = IO_HTTP(aio_username, aio_key, requests)
Error Checking
The reset_on_error()
function takes an Exception
error and resets the QT Py depending on the delay time.
The get_request()
function uses a try/except loop to attempt an HTTP request. In the event of an error, the request will be attempted again after a delay. Once the number of tries has been exceeded, then the loop breaks.
def reset_on_error(delay, error): print("Error:\n", str(error)) print("Resetting microcontroller in %d seconds" % delay) time.sleep(delay) microcontroller.reset() # function for making http requests with try/except def get_request(tries, ping): for i in range(tries): try: n = ping except Exception as error: print(error) time.sleep(10) if i < tries - 1: continue raise break return n
These two functions are utilized together in a try
/except
loop for each HTTP request. If the try
/except
loop exceeds the tries in get_request()
, then reset_on_error()
is called and the QT Py resets itself.
try: now = get_request(5, io.receive_time()) except Exception as e: reset_on_error(10, e)
When and Where Is The Sun?
The sun_clock()
function makes the Open-Meteo API request and returns that day's sunrise and sunset timestamp.
# function to make a request to open-meteo def sun_clock(): # make the API request response = get_request(5, requests.get(weather_url)) # packs the response into a JSON response_as_json = response.json() # gets sunrise _rise = response_as_json['daily']['sunrise'][0] # gets sunset _set = response_as_json['daily']['sunset'][0] return _rise, _set
However, the sunrise and sunset timestamps are returned as strings, which isn't very helpful for doing math. The divide_time()
function chops up the string and returns a struct_time
object.
def divide_time(z): string_time = z.split("-") clock_time = string_time[2].split("T") int_time = clock_time[1].split(":") event_time = time.struct_time( (int(string_time[0]), int(string_time[1]), int(clock_time[0]), int(int_time[0]), int(int_time[1]), 0, -1, -1, False) ) # print(event_time) return event_time
Then, the sun_countdown()
function calculates the time remaining until either sunrise or sunset.
# function that tracks how many hours/minutes until sunrise or sunset def sun_countdown(sun_event): n = get_request(5, io.receive_time()) remaining = time.mktime(sun_event) - time.mktime(n) r = remaining # print(remaining) # calculate the seconds remaining secs_remaining = remaining % 60 # pylint: disable=unused-variable remaining //= 60 # calculate the minutes remaining minutes_until = remaining % 60 remaining //= 60 # calculate the hours remaining hours_until = remaining % 24 remaining //= 24 return r, hours_until, minutes_until, n
Before the loop, it's determined if the sun has already set. This sets the state for star_glow
, which is used in the loop to turn the NeoPixels on or off.
# check to see if the star fragment should be lit up on start-up if total_until_set < 0: print("star glow true") star_glow = True percent_red = 255 percent_yellow = 125 # turn neopixels on using RGB values pixels.fill((percent_red, percent_yellow, 0)) pixels.show() else: print("star glow false") star_glow = False percent_red = 0 percent_yellow = 0 # turn neopixels on using RGB values pixels.fill((percent_red, percent_yellow, 0)) pixels.show()
The Loop
In the loop, ticks_ms()
is used to track time. Every 15 minutes, requests are sent to Open-Meteo and Adafruit IO to retrieve the sunrise and sunset times and the current time. Depending on whether or not star_glow
is True
determines if sunrise or sunset is tracked.
while True: try: # if it's daytime if not star_glow: # every 15 minutes... if first_run or ticks_diff(ticks_ms(), clock) > time_check: print("pinging Open-Meteo") sunrise, sunset = sun_clock() (total_until_set, hours_until_sunset, mins_until_sunset, now) = sun_countdown(set_time) print(now) print("%d hour(s) until sunset" % hours_until_sunset) print("%d minutes(s) until sunset" % mins_until_sunset) print(sunset) print(percent_red) print()
Mapping Color to Time
When there is less than an hour until sunrise or sunset, Open-Meteo and Adafruit IO begin to be pinged every 5 minutes. The NeoPixels begin to either dim or brighten during that hour countdown. Their red and green values are mapped to the minutes remaining.
Once the sun event has been reached, the NeoPixels are set to fully yellow or fully off, the time_check
is reset to 15 minutes, and the state of star_glow
is updated.
# less than an hour until sunset... if hours_until_sunset in (0, 23): # check every minute time_check = 300000 # map color to ramp up in brightness over the course of the final hour percent_red = simpleio.map_range(mins_until_sunset, 59, 0, 0, 255) percent_yellow = simpleio.map_range(mins_until_sunset, 59, 0, 0, 125) # if the sun has set.. if total_until_set < 0: percent_red = 255 percent_yellow = 125 time_check = 900000 star_glow = True print("star is glowing")
What Day Is It?
There is some additional logic in place to prevent errors while the star is glowing. Once the day changes (aka once it's past midnight), the looking_for_sunrise
state is set to True
to begin checking Open-Meteo for the sunrise and sunset times for the new day.
if first_run or ticks_diff(ticks_ms(), clock) > time_check: if today != now.tm_mday or (first_run and now.tm_hour < rise_time.tm_hour): today = now.tm_mday looking_for_sunrise = True # begin tracking the incoming sunrise if looking_for_sunrise: print("pinging Open-Meteo") sunrise, sunset = sun_clock() (total_until_rise, hours_until_sunrise, mins_until_sunrise, now) = sun_countdown(rise_time)
While the code waits for the new day, the time is checked every 15 minutes and the NeoPixels are fully yellow.
# otherwise just keep checking every 15 minutes # and keep neopixels on else: now = get_request(5, io.receive_time()) print("not looking for sunrise") print(now) print() time_check = 900000 percent_red = 255 percent_yellow = 125
Page last edited January 22, 2025
Text editor powered by tinymce.