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.