Code with CircuitPython

CircuitPython Setup

For this project you need to ensure your version of CircuitPython is at least 3.0. Adafruit recommends the latest version available by clicking the green button below. Download the file to your Downloads folder, Desktop, etc. (a place you'll remember for the next step).

To load the downloaded file, plug in the HalloWing over USB to your computer and then double-click the reset button on the HalloWing. This will put it into bootloader mode, allowing you to change the firmware. You'll see a USB drive appear on your computer called HALLOWBOOT.

Drag the .uf2 file you downloaded onto the HALLOWBOOT drive. Once it copies over, it will automatically restart, show up as a drive named CIRCUITPY and you will be ready to load a CircuitPython program.

Adafruit really likes using the Mu editor to edit the CircuitPython code. See this guide on loading and using Mu.

Libraries

You'll also need to add a code library for this project. Follow this guide on adding libraries. The only one you'll need is the adafruit_motor folder from the Circuit Python bundle in the lib folder, so just drag it from your downloaded, unzipped lib folder onto the HalloWing's lib folder.

Code Outline

Here's the basic flow of our trap:

  • Startup
  • Rotate the servo arm to the set position
  • Watch for the PIR sensor to be triggered
  • Play hissing sound over the speaker
  • Rotate the servo to drop the creepy bug
  • Wait for the screaming to subside
  • Rotate the servo back to the set position
  • Wait for the user to press the reset cap touch tooth button
  • Begin waiting for the PIR sensor to be tripped again

User Feedback

In order to help you (or your lab assistant Igor) in setting the trap, we'll use the LCD TFT screen on the HalloWing to provide status messages and prompts.

Here are the screens we'll use:

proximity_please_standby.bmp

proximity_trap_set.bmp

proximity_trap_sprung.bmp

proximity_reset_trap.bmp

The simplest way to us the HalloWing screen currently is as an image player to show pre-generated bitmap graphics. Download this .zip file and then uncompress it. Plug in you HalloWing and then drag the four .bmp files onto the root level of your HalloWing's CIRCUITPY USB drive.

Audio Playback

We'll use two methods of audio playback with the audio.play() -- for beeping we'll use the tone playback of a sine wave sample, and for hissing we'll use the audioio.WaveFile() playback method.

Download this wave file and drag it onto your HalloWing's CIRCUITPY drive at the root level.

Code

Here is the code we'll use. Copy it and then paste in Mu. Save it to your HalloWing as code.py

# HalloWing Jump Scare Trap
# use PIR sensor, speaker, and servo
import time
import array
import math
import board
import displayio
import pulseio
from adafruit_motor import servo
import digitalio
import touchio
import audioio
# Setup LED and PIR pins
LED_PIN = board.D13  # Pin number for the board's built in LED.
PIR_PIN = board.SENSE   # Pin port connected to PIR sensor output wire.
# Setup digital input for PIR sensor:
pir = digitalio.DigitalInOut(PIR_PIN)
pir.direction = digitalio.Direction.INPUT
# Setup digital output for LED:
led = digitalio.DigitalInOut(LED_PIN)
led.direction = digitalio.Direction.OUTPUT
# Setup servo
# servo = pulseio.PWMOut(board.D4, frequency=50)
pwm = pulseio.PWMOut(board.D4)
servo = servo.Servo(pwm)

# Setup cap touch button
ready_button = touchio.TouchIn(board.TOUCH1)

def servo_ready():
    servo.angle = 0
def servo_release():
    servo.angle = 90

# Set servo to ready position
servo_ready()
# Function for playing wav file, releasing servo
def play_wave():
    wave_file = open("hiss01.wav", "rb")  # open a wav file
    wave = audioio.WaveFile(wave_file)
    audio.play(wave)  # play the wave file
    led.value = True
    servo_release()
    print('Motion detected!')
    while audio.playing:  # turn on LED, turn servo
        pass
    wave_file.close()  # close the wav file
# Setup audio out pin
audio = audioio.AudioOut(board.A0)

# tone player setup for status beeps
tone_volume = 0.1  # Increase this to increase the volume of the tone.
frequency_hz = 880  # Set this to the Hz of the tone you want to generate.
length = 8000 // frequency_hz
sine_wave = array.array("H", [0] * length)
for i in range(length):
    sine_wave[i] = int((1 + math.sin(math.pi * 2 * i / length)) * tone_volume * (2 ** 15 - 1))
sine_wave_sample = audioio.RawSample(sine_wave)

# Function for beeping, usage: 'beep(3)' will beep 3x
def beep(count):
    for _ in range(count):
        audio.play(sine_wave_sample, loop=True)
        time.sleep(0.1)
        audio.stop()
        time.sleep(0.05)

# Function for counting down, usage: 'countdown(5)'
def countdown(count):
    for k in range(count):
        print(count - k)
        led.value = True
        time.sleep(0.1)
        led.value = False
        time.sleep(1)

# function for blinking, usage: 'blink(5, 0.2)'
def blink(count, speed):
    for _ in range(count):
        led.value = True
        time.sleep(speed)
        led.value = False
        time.sleep(speed)

# display setup
backlight = pulseio.PWMOut(board.TFT_BACKLIGHT)
splash = displayio.Group()
board.DISPLAY.show(splash)
max_brightness = 2 ** 15
backlight.duty_cycle = 0
# Image list
images = ["trap_sprung.bmp", "reset_trap.bmp", "please_standby.bmp",
          "trap_set.bmp"]
# Function for displaying images on HalloWing TFT screen
def show_image(filename):
    image_file = open(filename, "rb")
    odb = displayio.OnDiskBitmap(image_file)
    face = displayio.Sprite(odb, pixel_shader=displayio.ColorConverter(), position=(0, 0))
    backlight.duty_cycle = 0
    splash.append(face)
    # Wait for the image to load.
    board.DISPLAY.wait_for_frame()
    backlight.duty_cycle = max_brightness

beep(1)  # startup beep
show_image(images[2])  # waiting display
print('Stabilizing')
countdown(3)
print('Ready')
blink(3, 0.2)
beep(3)  # ready beeps
triggered = False
ready = True

show_image(images[3])  # ready display

while True:
    # Check PIR sensor
    pir_value = pir.value
    # Check touch button
    ready = ready_button.value

    if pir_value and triggered is not True:
        # PIR is detecting movement!
        play_wave()
        splash.pop()
        show_image(images[0])
        print('Triggered')
        countdown(8)
        blink(3, 0.2)
        beep(1)
        print('Please reset')
        led.value = False
        triggered = True
        servo_ready()
        splash.pop()
        show_image(images[1])

    if ready:  # touch sensor has been pressed
        beep(1)
        splash.pop()
        show_image(images[2])
        countdown(8)
        print('Ready.')
        blink(3, 0.2)
        beep(3)
        splash.pop()
        show_image(images[3])
        triggered = False

Next we'll test out the code and deploy the trap!

This guide was first published on Sep 29, 2018. It was last updated on Sep 29, 2018. This page (Code with CircuitPython) was last updated on Sep 16, 2019.