Skip to content

Color Timer with Circuit Python

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()

Leave a Reply

Your email address will not be published. Required fields are marked *