The CLUE version of this project is a bit more sophisticated than the CPX one, since we have a TFT display to work with and a proximity sensor.

We'll set up the board with CircuitPython and the necessary libraries first, and then code it.

Setup

First, follow this guide on getting CircuitPython and the on your CLUE board. Then, use these instructions to add the libraries you'll need.

Once you've gotten the board set up, click Download: Project Zip below in the code guide. Expand the .zip file and then drag the two .bmp images and the font folder to your CLUE's CIRCUITPY drive via USB.

Your CLUE's CIRCUITPY drive should look like this.

Code

Copy the code from the code-block below and paste it into the Mu editor and save it to your CLUE as code.py (or copy code.py from the zip file and place on the CIRCUITPY drive).

"""
Start a 20 second hand washing timer via proximity sensor.
Countdown the seconds with text and beeps.
Display a bitmaps for waiting and washing modes.
"""

import time
import board
from adafruit_clue import clue
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
import displayio
import pulseio

clue.display.brightness = 0.8
clue_display = displayio.Group(max_size=4)

# draw the background image
wash_on_file = open("wash_on.bmp", "rb")
wash_on_bmp = displayio.OnDiskBitmap(wash_on_file)
wash_on_sprite = displayio.TileGrid(wash_on_bmp, pixel_shader=displayio.ColorConverter())
clue_display.append(wash_on_sprite)

# draw the foreground image
wash_off_file = open("wash_off.bmp", "rb")
wash_off_bmp = displayio.OnDiskBitmap(wash_off_file)
wash_off_sprite = displayio.TileGrid(wash_off_bmp, pixel_shader=displayio.ColorConverter())
clue_display.append(wash_off_sprite)


# Create text
# first create the group
text_group = displayio.Group(max_size=5, scale=1)
# Make a label
title_font = bitmap_font.load_font("/font/RacingSansOne-Regular-38.bdf")
title_font.load_glyphs("HandWashTimer".encode('utf-8'))
title_label = label.Label(title_font, text="Hand Wash", color=0x001122)
# Position the label
title_label.x = 10
title_label.y = 16
# Append label to group
text_group.append(title_label)

title2_label = label.Label(title_font, text="Timer", color=0x001122)
# Position the label
title2_label.x = 6
title2_label.y = 52
# Append label to group
text_group.append(title2_label)

timer_font = bitmap_font.load_font("/font/RacingSansOne-Regular-29.bdf")
timer_font.load_glyphs("0123456789ADSWabcdefghijklmnopqrstuvwxyz:!".encode('utf-8'))
timer_label = label.Label(timer_font, text="Wave to start", color=0x4f3ab1, max_glyphs=15)
timer_label.x = 24
timer_label.y = 100
text_group.append(timer_label)

clue_display.append(text_group)
clue.display.show(clue_display)

def countdown(seconds):
    for i in range(seconds):
        buzzer.duty_cycle = 2**15
        timer_label.text = ("Scrub time:  {}".format(seconds-i))
        buzzer.duty_cycle = 0
        time.sleep(1)
    timer_label.text = ("Done!")
    wash_off_sprite.x = 0
    buzzer.duty_cycle = 2**15
    time.sleep(0.3)
    buzzer.duty_cycle = 0
    timer_label.x = 24
    timer_label.y = 100
    timer_label.text = ("Wave to start")

# setup buzzer
buzzer = pulseio.PWMOut(board.SPEAKER, variable_frequency=True)
buzzer.frequency = 1000

while True:
    # print("Distance: {}".format(clue.proximity))  # use to test the sensor
    if clue.proximity > 1:
        timer_label.x = 12
        timer_label.y = 226
        timer_label.text = "Scrub Away!"
        wash_off_sprite.x = 300
        time.sleep(2)
        countdown(20)

What the code is doing

The code does a few fairly simple things. First, we import the necessary libraries.

Download: file
import time
import board
from adafruit_clue import clue
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
import displayio
import pulseio

Display and Images

Next, we set the display brightness and then use the clue_display commands to set up the displayio group that will hold the bitmap images and text.

We then draw both images onto the screen, one on top of the other. (The reason we do this is for speed. By loading both images into memory at startup, it will be fast to later move the top image out of the way to reveal the bottom image.)

Download: file
# draw the background image
wash_on_file = open("wash_on.bmp", "rb")
wash_on_bmp = displayio.OnDiskBitmap(wash_on_file)
wash_on_sprite = displayio.TileGrid(wash_on_bmp, pixel_shader=displayio.ColorConverter())
clue_display.append(wash_on_sprite)
 
# draw the foreground image
wash_off_file = open("wash_off.bmp", "rb")
wash_off_bmp = displayio.OnDiskBitmap(wash_off_file)
wash_off_sprite = displayio.TileGrid(wash_off_bmp, pixel_shader=displayio.ColorConverter())
clue_display.append(wash_off_sprite)

Text Labels

Then we add groups for each line of text we'll be displaying. This involves specifying bitmap fonts (learn how to make your own in this guide!)

Again, to speed things up later, we'll preload the glyphs (a.k.a., characters) now. Notice that we're loading only the glyphs we intend to use to save time.

We also specify the text to be displayed, the colors, and positions in x/y screen space, and then append each text label to the text_group.

The clue.display.show(clue_display) command finally tells the screen to display the bitmaps and text lines.

Download: file
# Create text
# first create the group
text_group = displayio.Group(max_size=5, scale=1)
# Make a label
title_font = bitmap_font.load_font("/font/RacingSansOne-Regular-38.bdf")
title_font.load_glyphs("HandWashTimer".encode('utf-8'))
title_label = label.Label(title_font, text="Hand Wash", color=0x001122)
# Position the label
title_label.x = 10
title_label.y = 16
# Append label to group
text_group.append(title_label)
 
title2_label = label.Label(title_font, text="Timer", color=0x001122)
# Position the label
title2_label.x = 6
title2_label.y = 52
# Append label to group
text_group.append(title2_label)
 
timer_font = bitmap_font.load_font("/font/RacingSansOne-Regular-29.bdf")
timer_font.load_glyphs("0123456789ADSWabcdefghijklmnopqrstuvwxyz:!".encode('utf-8'))
timer_label = label.Label(timer_font, text="Wave to start", color=0x4f3ab1, max_glyphs=15)
timer_label.x = 24
timer_label.y = 100
text_group.append(timer_label)
 
clue_display.append(text_group)
clue.display.show(clue_display)

Countdown Function

We'll create a separate function called countdown() to handle the hand washing countdown process after the CLUE has been triggered.

It will accept an argument for the number of seconds you intend, which makes it easier to adjust if you like versus a hard coded value.

The function iterates through a loop for the number of seconds, incrementing the value of i each time. During each run through this loop it will buzz the buzzer, displaying the scrub time remaining in seconds, pause a second, and repeat.

When the loop finishes the last iteration (20 seconds, say), the wash_off.bmp sprite is moved into place, and the timer label resets to say "Wave to start".

Download: file
def countdown(seconds):
    for i in range(seconds):
        buzzer.duty_cycle = 2**15
        timer_label.text = ("Scrub time:  {}".format(seconds-i))
        buzzer.duty_cycle = 0
        time.sleep(1)
    timer_label.text = ("Done!")
    wash_off_sprite.x = 0
    buzzer.duty_cycle = 2**15
    time.sleep(0.3)
    buzzer.duty_cycle = 0
    timer_label.x = 24
    timer_label.y = 100
    timer_label.text = ("Wave to start")

Buzzer Setup

The buzzer is setup to use the pulsio() command and buzz the onboard speaker using a pulse wave modulation signal (PWM) set to a frequency of 1000.

When the buzzer.duty_cycle = 2**15 we'll hear the buzzing, and when the duty_cycle = 0 it will stop. These commands are found inside the countdown() function.

Download: file
buzzer = pulseio.PWMOut(board.SPEAKER, variable_frequency=True)
buzzer.frequency = 1000

Main Loop

Finally, we have finished setting things up and can run the main loop.

Here, we'll use clue.proximity to check the value of the CLUE's on-board proximity sensor. The value will read 0 when nothing is in front of the sensor. When an object -- such as your hand -- is about 8cm or closer, the sensor will report a value greater than 1 and trip the rest of the conditional statement to run.

In this case, we'll move the timer label to the bottom, replacing the text with "Scrub Away!", move the "off" bitmap out of the way to reveal the hand washing "on" bitmap, wait two seconds and then call the countdown function for 20 seconds.

Download: file
while True:
    # print("Distance: {}".format(clue.proximity))  # use to test the sensor
    if clue.proximity > 1:
        timer_label.x = 12
        timer_label.y = 226
        timer_label.text = "Scrub Away!"
        wash_off_sprite.x = 300
        time.sleep(2)
        countdown(20)

Now, you can mount the CLUE over your sink, run a USB power cable to it from an outlet, and wash your hands to a stylish timer!

This guide was first published on Apr 01, 2020. It was last updated on Apr 01, 2020.

This page (CLUE Hand Wash Timer) was last updated on Nov 06, 2020.