This color changing timer shows when an hour passes by going from blue to red in reverse rainbow order. It’s a subtle and non-intrusive reminder to get up and take a break without needing to watch a clock.
This was developed and tested on an Adafruit Circuit Playground Express, and a Circuit Playground Bluefruit (shown in the image with a transparent case). It should work on any Circuit Python board that has Neopixels and a button. Here are instructions on setting up a Circuit Playground Express with Circuit Python.
Copy the code to a file called code.py on the Circuit Playground. Power the board with USB or LiPo battery. On startup, the board will run code.py by default.
Press either button to start the timer, and again to stop it. Over the course of an hour it will change colors from blue to green, yellow, orange, and finally red. Press it when you start working and if you are still working when you notice it glowing red consider taking a break soon.
The duration of the timer is defined in the code by TIMER_DURATION, which is set to 3600 seconds (1 hour) by default.
import time
import math
import board
import neopixel
import digitalio
# Hardware setup
pixels = neopixel.NeoPixel(board.NEOPIXEL, 10, brightness=0.3, auto_write=False)
button_a = digitalio.DigitalInOut(board.BUTTON_A)
button_a.direction = digitalio.Direction.INPUT
button_a.pull = digitalio.Pull.DOWN
button_b = digitalio.DigitalInOut(board.BUTTON_B)
button_b.direction = digitalio.Direction.INPUT
button_b.pull = digitalio.Pull.DOWN
# Configuration
# Rainbow colors: blue → green → yellow → orange → red
RAINBOW_COLORS = [
(0, 150, 255), # Soft cyan/blue
(0, 255, 100), # Green
(255, 255, 0), # Yellow
(255, 165, 0), # Orange
(255, 0, 0) # Red
]
DIM_RED = (50, 0, 0) # Dim red for pulsing
BRIGHT_RED = (255, 0, 0) # Bright red for pulsing
TIMER_DURATION = 3600 # in seconds
UPDATE_INTERVAL = 10 # Update color every 10 seconds
PULSE_DURATION = 600 # 10 minutes of pulsing
# State variables
timer_active = False
timer_start_time = 0
last_update_time = 0
def interpolate_color(color1, color2, ratio):
"""Interpolate between two RGB colors based on ratio (0.0 to 1.0)"""
r1, g1, b1 = color1
r2, g2, b2 = color2
r = int(r1 + (r2 - r1) * ratio)
g = int(g1 + (g2 - g1) * ratio)
b = int(b1 + (b2 - b1) * ratio)
return (r, g, b)
def set_all_pixels(color):
"""Set all neopixels to the specified color"""
pixels.fill(color)
pixels.show()
def get_timer_color(elapsed_time):
"""Get the current color based on elapsed time during the timer phase"""
if elapsed_time >= TIMER_DURATION:
return RAINBOW_COLORS[-1] # Return final red color
# Calculate which color segment we're in and the progress within that segment
num_segments = len(RAINBOW_COLORS) - 1 # 4 segments between 5 colors
segment_duration = TIMER_DURATION / num_segments # 15 minutes per color transition
segment_index = int(elapsed_time // segment_duration)
if segment_index >= num_segments:
segment_index = num_segments - 1
# Calculate progress within the current segment (0.0 to 1.0)
segment_progress = (elapsed_time % segment_duration) / segment_duration
# Interpolate between current and next rainbow color
current_color = RAINBOW_COLORS[segment_index]
next_color = RAINBOW_COLORS[segment_index + 1]
return interpolate_color(current_color, next_color, segment_progress)
def get_pulse_brightness(elapsed_pulse_time):
"""Get brightness multiplier for pulsing phase"""
# Calculate how far into the pulse phase we are (0.0 to 1.0)
phase_ratio = elapsed_pulse_time / PULSE_DURATION
# Pulse frequency increases over time
# Start with slow pulses (period of 4 seconds), end with fast pulses (period of 0.5 seconds)
min_period = 4.0
max_period = 0.5
current_period = min_period - (min_period - max_period) * phase_ratio
# Calculate pulse brightness using sine wave
pulse_position = (elapsed_pulse_time % current_period) / current_period
brightness = 0.5 + 0.5 * math.sin(pulse_position * 2 * math.pi)
return brightness
def get_pulse_color(elapsed_pulse_time):
"""Get the current pulsing color"""
brightness = get_pulse_brightness(elapsed_pulse_time)
r = int(DIM_RED[0] + (BRIGHT_RED[0] - DIM_RED[0]) * brightness)
g = int(DIM_RED[1] + (BRIGHT_RED[1] - DIM_RED[1]) * brightness)
b = int(DIM_RED[2] + (BRIGHT_RED[2] - DIM_RED[2]) * brightness)
return (r, g, b)
def start_timer():
"""Start the timer"""
global timer_active, timer_start_time, last_update_time
timer_active = True
timer_start_time = time.monotonic()
last_update_time = timer_start_time
set_all_pixels(RAINBOW_COLORS[0])
print("Timer started - rainbow transition: blue → green → yellow → orange → red over 1 hour")
def stop_timer():
"""Stop the timer and turn off pixels"""
global timer_active
timer_active = False
print("Timer stopped")
def turn_off_pixels():
"""Turn off all pixels"""
set_all_pixels((0, 0, 0))
def main():
global last_update_time
print("Circuit Playground Timer Ready")
print("Press either button to start the timer")
# Turn off all pixels initially
set_all_pixels((0, 0, 0))
while True:
current_time = time.monotonic()
# Check for button presses
if button_a.value or button_b.value:
if not timer_active:
start_timer()
else:
stop_timer()
turn_off_pixels()
# Wait for button release to avoid multiple triggers
while button_a.value or button_b.value:
time.sleep(0.1)
if timer_active:
elapsed_time = current_time - timer_start_time
if elapsed_time < TIMER_DURATION:
# Timer phase: transition from blue to red
if current_time - last_update_time >= UPDATE_INTERVAL:
color = get_timer_color(elapsed_time)
set_all_pixels(color)
last_update_time = current_time
# Print progress every minute
minutes_elapsed = int(elapsed_time // 60)
if elapsed_time % 60 < UPDATE_INTERVAL:
print("Timer: {} minutes elapsed".format(minutes_elapsed))
elif elapsed_time < TIMER_DURATION + PULSE_DURATION:
# Pulse phase: pulse red with increasing frequency
elapsed_pulse_time = elapsed_time - TIMER_DURATION
color = get_pulse_color(elapsed_pulse_time)
set_all_pixels(color)
# Print pulse phase progress
if int(elapsed_pulse_time) % 60 == 0 and elapsed_pulse_time % 60 < 0.1:
minutes_in_pulse = int(elapsed_pulse_time // 60)
print("Pulse phase: {} minutes".format(minutes_in_pulse))
else:
# Timer complete
print("Timer complete - stopping")
stop_timer()
time.sleep(0.1) # Small delay to prevent excessive CPU usage
if __name__ == "__main__":
main()
