Download a zip of the project by clicking Download Project Bundle below.
After unzipping the file, you'll need to edit code.py. I'll explain the edits that need to be made below.
Before you do that though, you should copy the required libraries over:
- adafruit_bus_device
- adafruit_register
- adafruit_lc709203f.mpy
- adafruit_pcf8523.mpy
- adafruit_pixelbuf.mpy
- neopixel.mpy
- simpleio.mpy
- adafruit_led_animation
# SPDX-FileCopyrightText: 2022 Eva Herrada for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import time
import board
from adafruit_lc709203f import LC709203F
from adafruit_pcf8523.pcf8523 import PCF8523
from simpleio import tone
import neopixel
from adafruit_led_animation.animation.rainbow import Rainbow
i2c = board.I2C() # uses board.SCL and board.SDA
# i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller
rtc = PCF8523(i2c)
battery = LC709203F(i2c)
indicator = neopixel.NeoPixel(board.A1, 1)
LEDs = neopixel.NeoPixel(board.A2, 20)
LEDs.fill((0, 0, 0))
rainbow = Rainbow(LEDs, speed=0.1, period=2)
days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
sleep = {
0: (22, 50),
1: (23, 8),
2: (22, 50),
3: (22, 50),
4: (22, 50),
5: None,
6: None,
}
wake = {
0: (11, 30),
1: (9, 50),
2: (9, 50),
3: (9, 50),
4: (9, 50),
5: (9, 50),
6: (11, 30),
}
BPM = 160
ringtone = [
(330, 0.5),
(294, 0.5),
(185, 1),
(208, 1),
(277, 0.5),
(247, 0.5),
(147, 1),
(165, 1),
(247, 0.5),
(220, 0.5),
(139, 1),
(165, 1),
(220, 2),
]
SET_DATE = False # Set this to True on first run and False for subsequent runs
if SET_DATE:
# Make sure to set this to the current date and time before using
YEAR = 2022
MONTH = 3
DAY = 21
HOUR = 16
MINUTE = 54
SEC = 0
WDAY = 1 # 0 Sunday, 1 Monday, etc.
t = time.struct_time((YEAR, MONTH, DAY, HOUR, MINUTE, SEC, WDAY, -1, -1))
print("Setting time to:", t) # uncomment for debugging
rtc.datetime = t
print()
on_always = True # Set to False for animation only to be on when alarm rings
indicate = True
start = time.monotonic()
wait = 1
while True:
if on_always:
rainbow.animate()
if time.monotonic() - start > wait:
start = time.monotonic()
wait = 1
t = rtc.datetime
bat = min(max(int(battery.cell_percent), 0), 100)
g = int((bat / 100) * 255)
r = int((1 - (bat / 100)) * 255)
if bat >= 15:
indicator.fill([r, g, 0])
if bat < 15:
if indicate:
indicator.fill([0, 0, 0])
indicate = False
else:
indicator.fill([r, g, 0])
indicate = True
print(f"Date: {days[t.tm_wday]} {t.tm_mday}/{t.tm_mon}/{t.tm_year}")
print(f"Time: {t.tm_hour}:{t.tm_min}:{t.tm_sec}")
print(f"Batt: {bat}%\n")
night = sleep[t.tm_wday]
morning = wake[t.tm_wday]
if night:
if night[0] == t.tm_hour and night[1] == t.tm_min:
for i in ringtone:
print(i[0])
tone(board.A0, i[0], i[1] * (60 / BPM))
wait = 60
if not on_always:
while time.monotonic() - start < 5:
rainbow.animate()
rainbow.fill([0, 0, 0])
if morning:
if morning[0] == t.tm_hour and morning[1] == t.tm_min:
for i in ringtone:
print(i[0])
tone(board.A0, i[0], i[1] * (60 / BPM))
wait = 60
if not on_always:
while time.monotonic() - start < 5:
rainbow.animate()
rainbow.fill([0, 0, 0])
Set all the variables circled below to the current date and time.
Now that you have the time set in the code, copy cody.py to the CIRCUITPY drive which appears in your operating system File Explorer or Finder when the Feather is connected to your computer via a USB cable that is known to work for data transfer.
You'll now want to set the SET_DATE variable to False and copy it over to the CIRCUITPY drive again.
After you've copied all that over, your CIRCUITPY drive should look something like this:
Code run-through
In this section I'll go through how the code works and what does what. I'd recommend reading this if you're planning on modifying the code but it's also very useful to read since part of this project involves setting the time and day for your alarms to go off.
First, the code makes the required imports.
import time import board from adafruit_lc709203f import LC709203F import adafruit_pcf8523 from simpleio import tone import neopixel from adafruit_led_animation.animation.rainbow import Rainbow
Next, the code sets up the RTC, the LiPo charge indicator, the LED indicator, and the LED strip.
rtc = adafruit_pcf8523.PCF8523(board.I2C()) battery = LC709203F(board.I2C()) indicator = neopixel.NeoPixel(board.A1, 1) LEDs = neopixel.NeoPixel(board.A2, 20) LEDs.fill((0, 0, 0)) rainbow = Rainbow(LEDs, speed=0.1, period=2)
This next section is where the alarms are defined. The list titled days is used to make printing the day easier and shouldn't be changed.
The dictionary titled sleep is intended to be used as a reminder for the evening, and the dictionary titled wake is intended to be used as a reminder in the morning.
You set the alarm by setting it as a tuple in the value of the dict item. The key (0, 1, 2, etc.) denotes the day, with 0 being Sunday. The first item in the tuple is the hour, and the second item is the minute.
If you don't want an alarm to ring on that day, set the value of the dict item for that day to None.
days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
sleep = {
0: (22, 50),
1: (22, 50),
2: (22, 50),
3: (22, 50),
4: (22, 50),
5: None,
6: None,
}
wake = {
0: (11, 30),
1: (9, 50),
2: (9, 50),
3: (9, 50),
4: (9, 50),
5: (9, 50),
6: (11, 30),
}
Next, the BPM that the alarm sound will play in is set and the alarm itself is set as well. Each tuple in the list is a note. The first item in the tuple is the frequency, and the second item is the duration, in beats. If you'd like to change this, one website I found very handy was this tone generator. It has a list of notes and their frequencies if you click the box next to the music note.
BPM = 160
ringtone = [
(330, 0.5),
(294, 0.5),
(185, 1),
(208, 1),
(277, 0.5),
(247, 0.5),
(147, 1),
(165, 1),
(247, 0.5),
(220, 0.5),
(139, 1),
(165, 1),
(220, 2),
]
This next section sets up the RTC. The first time you run the code, make sure to edit all the values (YEAR, MONTH, etc.) to the current date and time. After you first copy this over to the RP2040, you'll want to set SET_DATE to False and copy it over again. This ensures that the RTC won't get incorrectly set accidentally.
SET_DATE = True
if SET_DATE:
# Make sure to set this to the current date and time before using
YEAR = 2022
MONTH = 3
DAY = 10
HOUR = 22
MINUTE = 49
SEC = 55
WDAY = 0 # 0 Sunday, 1 Monday, etc.
t = time.struct_time((YEAR, MONTH, DAY, HOUR, MINUTE, SEC, WDAY, -1, -1))
print("Setting time to:", t) # uncomment for debugging
rtc.datetime = t
print()
The code then sets a few variables and enters the main loop. First, the code updates the animation if that is set to be always on, then checks if enough time has passed to run the alarm logic again. If it has, the code gets the datetime from the RTC and the battery percentage from the LiPo meter. It then sets the color of the indicator LED to a gradient between red and green (full red being 0%, and full green being 100%).
If the battery is below 15%, the indicator LED will flash on and off.
on_always = True # Set to False for animation only to be on when alarm rings
indicate = True
start = time.monotonic()
wait = 1
while True:
if on_always:
rainbow.animate()
if time.monotonic() - start > wait:
start = time.monotonic()
wait = 1
t = rtc.datetime
bat = min(max(int(battery.cell_percent), 0), 100)
g = int((bat / 100) * 255)
r = int((1 - (bat / 100)) * 255)
if bat >= 15:
indicator.fill([r, g, 0])
if bat < 15:
if indicate:
indicator.fill([0, 0, 0])
indicate = False
else:
indicator.fill([r, g, 0])
indicate = True
For the second part of the main loop, the code prints a bunch of information that's useful for when you're connected to the RP2040 from a serial console and sets two variables that are the alarm times in the sleep and wake lists.
⠀ print(f"The date is {days[t.tm_wday]} {t.tm_mday}/{t.tm_mon}/{t.tm_year}")
print(f"The time is {t.tm_hour}:{t.tm_min}:{t.tm_sec}")
print(f"Battery: {bat}%")
night = sleep[t.tm_wday]
morning = wake[t.tm_wday]
The code then checks each of the two variables defined above to see if the user wants an alarm to be rung today. If so it then checks to see if it's time to ring that alarm. If it is, it plays the notes from the alarm sound and waits 60 seconds to ensure the alarm doesn't go off for the entire minute.
The code then waits for a second and runs the loop again.
⠀ if night:
if night[0] == t.tm_hour and night[1] == t.tm_min:
for i in ringtone:
print(i[0])
tone(board.A0, i[0], i[1] * (60 / BPM))
wait = 60
if not on_always:
while time.monotonic() - start < 5:
rainbow.animate()
rainbow.fill([0, 0, 0])
if morning:
if morning[0] == t.tm_hour and morning[1] == t.tm_min:
for i in ringtone:
print(i[0])
tone(board.A0, i[0], i[1] * (60 / BPM))
wait = 60
if not on_always:
while time.monotonic() - start < 5:
rainbow.animate()
rainbow.fill([0, 0, 0])
Page last edited January 21, 2025
Text editor powered by tinymce.