The next step is to prepare the software on your QT Py ESP32-S3.
Copy the Code and Libraries
You need to copy the code and all of the necessary libraries to your QT Py.
Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, and copy the entire lib folder and the code.py file to your CIRCUITPY drive.
Your CIRCUITPY drive should contain the following files and folders, as well as your settings.toml file.
# SPDX-FileCopyrightText: 2023 Kattni Rembor for Adafruit Industries # SPDX-License-Identifier: MIT """ CircuitPython Canary Day and Night Light with Optional Network-Down Detection This project uses the QT Py ESP32-S3 with the NeoPixel 5x5 LED Grid BFF, along with a 3D printed bird. The LEDs light up different colors based on the time. In the event that the internet connection fails, it will begin blinking red to notify you. If the initial test ping fails, and the subsequent pings fail over 30 times, the board will reset. Otherwise, the blinking will continue until the connection is back up. This feature is enabled by default. It can easily be disabled at the beginning of the code. """ import os import ssl import time import ipaddress import supervisor import board import wifi import microcontroller import socketpool import adafruit_requests import neopixel from adafruit_io.adafruit_io import IO_HTTP # ============ CUSTOMISATIONS ============ # Network-down detection enable or disable. # By default, the network-down detection code, and the code that blinks when the # network is down, are both enabled. If you wish to disable this feature, # including the blinking, update this to False. NETWORK_DOWN_DETECTION = True # The basic canary colors. # Red light at night is more conducive to sleep. Blue light in the morning is more # conducive to waking up. The sleep color defaults to red to promote sleep. The wake # color defaults to blue to promote wakefulness. SLEEP_COLOR = (255, 0, 0) # Red WAKE_COLOR = (0, 0, 255) # Blue # Sleep time. # This is the hour in 24-hour time at which the light should change to the # desired color for the time you intend to sleep. # Must be an integer between 0 and 23. Defaults to 20 (8pm). SLEEP_TIME = 20 # Wake time. # This is the hour in 24-hour time at which the light should change to the # desired color for the time you intend to be awake. # Must be an integer between 0 and 23. Defaults to 6 (6am). WAKE_TIME = 6 # Canary brightness customisation. # Brightness must be a float or integer between 0.0 and 1.0, where 0.0 is off, and 1.0 is max. # This is the brightness of the canary during sleep time. It defaults to 0.2, or "20%". # Increase or decrease this value to change the brightness. SLEEP_BRIGHTNESS = 0.2 # This is the brightness of the canary during wake time. It defaults to 0.7, or "70%". # Increase or decrease this value to change the brightness. WAKE_BRIGHTNESS = 0.7 # Time check interval. # This sets the time interval at which the code checks Adafruit IO for the current time. # This is included because Adafruit IO has rate limiting. It ensures that you do not # hit the rate limit, and the time check does not get throttled. # Defaults to 300 seconds (5 minutes). Must be an integer equal to or greater than 300. # Increase this value to increase the time check interval. Do not decrease this value! TIME_CHECK_INTERVAL = 300 # Checks whether the time check interval is below the minimum value and an integer. if TIME_CHECK_INTERVAL < 300 or isinstance(TIME_CHECK_INTERVAL, float): # If is below the minimum or a float, raise this error and stop the code. raise ValueError("TIME_CHECK_INTERVAL must be a integer, and greater than 300!") # Ping interval while ping is successful. # This is the interval at which the code will send a ping while the network is up and the pings # are successful. If for any reason you would prefer to slow down the ping interval, this value # can be updated. Defaults to 1 second. Must be an integer equal to or greater than 1. Increase # this value to increase the ping interval time. Do not decrease this value! UP_PING_INTERVAL = 1 # Checks whether the successful ping interval is below the minimum value and an integer. if UP_PING_INTERVAL < 1 or isinstance(UP_PING_INTERVAL, float): # If is below the minimum or a float, raise this error and stop the code. raise ValueError("UP_PING_INTERVAL must be a integer, and greater than 1!") # The blink color. # This is the color that the canary will blink to notify you that the network is down. # Defaults to red. BLINK_COLOR = (255, 0, 0) # Consecutive ping fail to blink. # This value is the number of times ping will consecutively fail before the canary begins blinking. # If the blinking is happening too often, or if the network is often flaky, this value can be # increased to extend the number of failures it takes to begin blinking. # Defaults to 10. Must be an integer greater than 1. CONSECUTIVE_PING_FAIL_TO_BLINK = 10 # The amount of time in seconds that needs to pass while the network is down AND # NETWORK_DOWN_DETECTION is DISABLED before the board resets to try again. # Defaults to 900 seconds, or 20 minutes. Must be an integer. Increase or decrease # this value to alter how long the network should be down in this specific case # before the board resets. NETWORK_DOWN_RELOAD_TIME = 900 # IP address. # This is the IP address used to ping to verify that network connectivity is still present. # To switch to a different IP, update the following. Must be a valid IPV4 address as a # string (in quotes). Defaults to one of the OpenDNS IPs. PING_IP = "208.67.222.222" # ============ HARDWARE AND CODE SET UP ============ # Instantiate the NeoPixel object. pixels = neopixel.NeoPixel(board.A3, 25) # Create helper functions def reload_on_error(delay, error_content=None, reload_type="reload"): """ Reset the board when an error is encountered. :param float delay: The delay in seconds before the board should reset. :param Exception error_content: The error encountered. Used to print the error before reset. :param str reload_type: The type of reload desired. Defaults to "reload", which invokes ``supervisor.reload()`` to soft-reload the board. To hard reset the board, set this to "reset", which invokes ``microcontroller.reset()``. """ if str(reload_type).lower().strip() not in ["reload", "reset"]: raise ValueError("Invalid reload type:", reload_type) if error_content: print("Error:\n", str(error_content)) if delay: print( f"{reload_type[0].upper() + reload_type[1:]} microcontroller in {delay} seconds." ) time.sleep(delay) if reload_type == "reload": supervisor.reload() if reload_type == "reset": microcontroller.reset() def color_time(current_hour): """ Verifies what color the LEDs should be based on the time. :param current_hour: Provide a time, hour only. The `tm_hour` part of the `io.receive_time()` object is acceptable here. """ if WAKE_TIME < SLEEP_TIME: if WAKE_TIME <= current_hour < SLEEP_TIME: pixels.brightness = WAKE_BRIGHTNESS return WAKE_COLOR pixels.brightness = SLEEP_BRIGHTNESS return SLEEP_COLOR if SLEEP_TIME <= current_hour < WAKE_TIME: pixels.brightness = SLEEP_BRIGHTNESS return SLEEP_COLOR pixels.brightness = WAKE_BRIGHTNESS return WAKE_COLOR def blink(color): """ Blink the NeoPixel LEDs a specific color. :param tuple color: The color the LEDs will blink. """ if color_time(sundial.tm_hour) == SLEEP_COLOR: pixels.brightness = SLEEP_BRIGHTNESS else: pixels.brightness = WAKE_BRIGHTNESS pixels.fill(color) time.sleep(0.5) pixels.fill((0, 0, 0)) time.sleep(0.5) # Connect to WiFi. This process can fail for various reasons. It is included in a try/except # block to ensure the project continues running when unattended. try: wifi.radio.connect(os.getenv("wifi_ssid"), os.getenv("wifi_password")) pool = socketpool.SocketPool(wifi.radio) requests = adafruit_requests.Session(pool, ssl.create_default_context()) except Exception as error: # pylint: disable=broad-except # The exceptions raised by the `wifi` module are not always clear. If you're receiving errors, # check your SSID and password before continuing. print("Wifi connection failed.") reload_on_error(5, error) # Set up IP address to use for pinging, as defined above. ip_address = ipaddress.IPv4Address(PING_IP) # Set up ping, and send initial ping. wifi_ping = wifi.radio.ping(ip=ip_address) # If the initial ping is unsuccessful, print the message. if wifi_ping is None: print("Set up test-ping failed.") # Set `initial_ping` to False to indicate the failure. initial_ping = False else: # Otherwise, print this message. print("Set up test-ping successful.") # Set `initial_ping` to True to indicate success. initial_ping = True # Set up Adafruit IO. This will provide the current time through `io.receive_time()`. io = IO_HTTP(os.getenv("aio_username"), os.getenv("aio_key"), requests) # Retrieve the time on startup. This is included to verify that the Adafruit IO set # up was successful. This process can fail for various reasons. It is included in a # try/except block to ensure the project continues to run when unattended. try: sundial = io.receive_time() # Create the sundial variable to keep the time. except Exception as error: # pylint: disable=broad-except # If the time retrieval fails with an error, print the message. print("Adafruit IO set up and/or time retrieval failed.") # Then wait 5 seconds, and soft reload the board. reload_on_error(5, error) # Initialise various time tracking variables. check_time = 0 network_down_time = time.time() ping_time = 0 ping_fail_time = time.time() # Initialise network check variable. network_check = 1 # Initialise ping fail count tracking. ping_fail_count = 0 # ============ LOOP ============ while True: # Resets current_time to the current second every time through the loop. current_time = time.time() # WiFi and IO connections can fail arbitrarily. The bulk of the loop is included in a # try/except block to ensure the project will continue to run unattended if any # failures do occur. try: # If this is the first run of the code or the time check interval has passed, continue. if not check_time or current_time - check_time >= TIME_CHECK_INTERVAL: # Send a single ping to test for network connectivity. network_check = wifi.radio.ping(ip=ip_address) # If there is network connectivity, run the time check code. if network_check is not None: # Reset `check_time` to continue tracking. check_time = time.time() # Retrieve the time and save it to `sundial`. sundial = io.receive_time() # Print the current date and time to the serial console. print(f"LED color time-check. Date and time: {sundial.tm_year}-{sundial.tm_mon}-" + f"{sundial.tm_mday} {sundial.tm_hour}:{sundial.tm_min:02}") # Provide the current hour to the `color_time` function. The returned color is # provided to `pixels.fill()` to set the LED color. pixels.fill(color_time(sundial.tm_hour)) else: print("Network check ping failed.") # If network down detection is disabled AND the network check ping failed # AND the specified network down reload time passed: print the message, # wait 3 seconds, and hard reset the board. if not NETWORK_DOWN_DETECTION and network_check is None and \ current_time - network_down_time > NETWORK_DOWN_RELOAD_TIME: print(f"Network check ping has failed for over {NETWORK_DOWN_RELOAD_TIME} seconds.") reload_on_error(3, reload_type="reset") # If network down detection is enabled, run the rest of the code. if NETWORK_DOWN_DETECTION: # If this is the first run of the code or up ping interval` time has passed, continue. if not ping_time or current_time - ping_time >= UP_PING_INTERVAL: # Reset `ping_time` to continue tracking. ping_time = time.time() # Ping to verify network connection. wifi_ping = wifi.radio.ping(ip=ip_address) # If the ping is successful, set the fail count to 0, and print IP and ping time. if wifi_ping is not None: ping_fail_count = 0 print(f"Pinging {ip_address}: {wifi_ping} ms") # If the ping has failed, and it's been one second, continue with this code. if wifi_ping is None and current_time - ping_fail_time >= 1: # Reset `ping_fail_time` to continue tracking. ping_fail_time = time.time() # Add one to the failure count tracking. ping_fail_count += 1 # Print the ping failure count. print(f"Ping failed {ping_fail_count} times") # If the ping fail count exceeds the value defined above, begin blinking the LED # to indicate that the network is down. if ping_fail_count > CONSECUTIVE_PING_FAIL_TO_BLINK: blink(BLINK_COLOR) # If the initial setup ping failed, it means the network connection was failing # from the beginning, and might require reloading the board. So, if the initial # ping failed and the ping_fail_count is greater than 30, immediately soft reload # the board. if not initial_ping and ping_fail_count > 30: reload_on_error(0) # If the initial ping succeeded, the blinking will continue until the connection # is reestablished and the pings are once again successful. # ============ ERROR HANDLING ============ # Since network-related code can fail arbitrarily in a variety of ways, this final block is # included to reset the board when an error is encountered. # When the error is thrown, wait 10 seconds, and hard reset the board. except Exception as error: # pylint: disable=broad-except reload_on_error(10, error, reload_type="reset")
The settings.toml File
On your CIRCUITPY drive, you should find a settings.toml file. This is the file that is used to store your WiFi and Adafruit IO credentials, so you can use them in your code, without needing to add them directly to your code.py.
If you don't see the file, you can make one with any text editor on your computer by putting in the information below.
If you do not have an Adafruit IO account, see the previous page on how to create one.
Edit the settings.toml file to include the following. Update the information in quotes to match your credentials.
wifi_ssid = "your-wifi-ssid" wifi_password = "your-wifi-password" aio_username = "your-adafruit-io-username" aio_key = "your-long-adafruit-io-key"
Once settings.toml is updated, you're ready to continue!
Customize Your Canary
There are a number of features in this project that you can customize including the nightlight colors! This section will go through each feature, and cover the default and how to update it.
Network-Down Detection
The canary is watching over you with this feature! The light includes a feature that verifies whether you still have network connectivity, and notifies you when your network is down by blinking. That way you don't have to sit there and wonder if it's you or the internet!
If you have the canary sitting on your desk in your office, this is perfect. However, if you're using this light in your bedroom, it may not be so desirable. Even though the blinking is dimmer while you sleep, you may not want it to blink at all. The first thing in this code that you can customize is whether that blink code will run.
The code defaults to the blinking feature being enabled. To disable this feature, you need to change the following variable to False
.
NETWORK_DOWN_DETECTION = True
Sleep and Wake Time Canary Colors
Red light is conducive to sleep. Blue light is conducive to promoting wakefulness. Therefore, the canary default colors are red for when you intend to sleep, and blue for when you intend to be awake.
Obviously these choices have a purpose, but what's the point of a DIY light if you can't customize the colors? Here you can choose any color of the rainbow for your canary.
The sleep color is the color your canary will light up during the later-specified sleep time. The wake color is color during the later-specified wake time.
To update either color, you'll change the associated RGB color tuples to the desired colors.
SLEEP_COLOR = (255, 0, 0) WAKE_COLOR = (0, 0, 255)
Sleep and Wake Time
One of the main features of this project is that you can change the color that the canary lights up based on a specified time. This is where you set the times by providing an hour in 24-hour time; it must be an integer (whole number) between 0
and 23
.
Sleep Time
This is the hour at which the light should change to the desired color for the time you intend to be sleepy or sleep. It defaults to 20
, or 8pm.
SLEEP_TIME = 20
Wake Time
This is the hour at which the light should change to the desired color for the time you intend to promote wakefulness or be awake. It defaults to 6
, or 6am.
WAKE_TIME = 6
Canary Brightness
The NeoPixel LEDs used in this project can get very bright. That said, how bright your canary should be while you're awake and asleep is an entirely personal choice.
NeoPixel brightness is provided as a float (a number with a decimal) between 0.0
and 1.0
, where 0.0
is off, and 1.0
is max brightness. So, for example, if you wanted the LEDs to be quite dim, you might choose 0.1
. Alternatively, if you want them much brighter than that but not max, you might choose 0.8
.
The brightness during sleep time is defaults to 0.2
. The brightness during wake time defaults to 0.7
.
To increase or decrease these brightnesses, update the values as explained above.
SLEEP_BRIGHTNESS = 0.2 WAKE_BRIGHTNESS = 0.7
Time Check Interval
This sets the interval at which the code checks with Adafruit IO to receive the current time. It is required because Adafruit IO has rate-limiting in place, and ensures that you do not hit that limit and end up throttled. It defaults to 300
seconds, or 5 minutes.
If you wish to increase the delay between time checks, you can increase this value.
It must be a number of seconds greater than or equal to 300
. If you enter a value less than 300, the code will fail with an error.
TIME_CHECK_INTERVAL = 300
Successful Ping Interval
This is the interval at which the code will send a ping specifically while the network is connected, and the pings are successful. This defaults to 1
second.
If for any reason you wish to slow down the delay between successful pings, you can increase this value.
This value must be 1 second or greater. The code will fail with an error if you decrease this value below 1.
UP_PING_INTERVAL = 1
Blink Color
When the network-down detection is enabled, your canary will blink when the network is down. The blink color defaults to red, but you can change it to any color of the rainbow.
To update the color, you'll change the RGB color tuple to the desired color.
BLINK_COLOR = (255, 0, 0)
Consecutive Ping Fail to Blink
This value is the number of times the pings must consecutively fail before the canary begins blinking. It defaults to 10
consecutive failures.
Network connections in some places are flakier than others. If you feel like the blinking is happening too often, you can increase this value to extend the number of consecutive failures it takes to begin blinking.
CONSECUTIVE_PING_FAIL_TO_BLINK = 10
Network Down Reload Time
This is the amount of time in seconds that needs to pass before resetting the board, but only when network down detection is disabled. It is in place to ensure that if your network goes down, and you're not tracking it to use the canary to notify you, the board will eventually reset to attempt to reestablish a connection. It defaults to 900
seconds, or 20 minutes.
You can update this to whatever time interval in seconds you want, but know that the network check only occurs every 5 minutes or more when network down detection is disabled. So, if you set it to something less than 5 minutes, it will reload the board on the first failure.
NETWORK_DOWN_RELOAD_TIME = 900
Ping IP Address
This is the IP address used with ping to verify that network connectivity is still present. It defaults to 208.67.222.222
, which is one of the Open DNS IP addresses.
To switch, update this to a different IP. It must be a valid IPV4 address as a string (in quotes).
Be considerate when choosing an IP address. Make sure whatever you choose is prepared to handle the repeated pings. If you are concerned, remember you can decrease the ping time interval as well.
PING_IP = "208.67.222.222"
Text editor powered by tinymce.