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
import adafruit_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 = adafruit_pcf8523.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])

Setting the time

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:

CIRCUITPY

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 (YEARMONTH, 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])

This guide was first published on Mar 22, 2022. It was last updated on Mar 22, 2022.

This page (Code the Blahaj) was last updated on May 31, 2023.

Text editor powered by tinymce.