A motion-activated spider for Halloween with an Arduino and a Raspberry Pi
Every year my family hangs a large decorative spider over our doorway for Halloween. This year I decided to make it flash red LED eyes and play a random assortment of spooky sounds (including, but not limited to, bats, chains and the Vincent Price laugh from "Thriller") when trick-or-treaters come to the door. In today's blog post, I'll show how I did it.
The approach
A few years ago, I wrote a post about setting up a motion-activated webcam to trigger license plate recognition with a Raspberry Pi, so I already had a Raspberry Pi and an HC-SR04 ultrasonic rangefinder I could use to detect motion. I initially thought I would only need the Pi for this project, but as I started evaluating the physical constraints of my porch, I realized it would be difficult to mount the Pi in a secure location where it could handle motion detection and also flash the LED eyes. I decided a better approach would be to use an Arduino to detect motion and flash the LEDs, while the Pi would communicate with the Arduino over a serial connection and play Halloween sound effect MP3s.
Setting up the Arduino
I have two red LEDs taped over the spider's eyes that are connected in series to pin 12 on an Arduino UNO. The ultrasonic rangefinder is taped to the side of my porch so that trick-or-treaters have to pass by it before they can knock on my door, and it is connected to pin 9 for the trigger and pin 10 for the echo. The Arduino listens for serial commands from the Pi to measure distance or to flash the LEDs if the Pi determines the reported distance measurement means someone has approached.
Here's my sketch:
//set the pin numbers
const int triggerPin = 9;
const int echoPin = 10;
const int ledPin = 12;
void setup()
{
Serial.begin(9600); // Starts the serial communication
pinMode(triggerPin, OUTPUT); // Sets the triggerPin as an Output
pinMode(echoPin, INPUT); // Sets the echoPin as an Input
pinMode(ledPin, OUTPUT);
}
void loop()
{
if (Serial.available() > 0) {
int controlcode = Serial.parseInt();
if(controlcode==1)
{
//clear the trigger pin
digitalWrite(triggerPin, LOW);
delayMicroseconds(2);
//send a 10 microsecond pulse
digitalWrite(triggerPin, HIGH);
delayMicroseconds(10);
digitalWrite(triggerPin, LOW);
//read the echo pin to get the duration in microseconds
long duration = pulseIn(echoPin, HIGH);
//calculate the distance in centimeters
int distance = duration*0.034/2;
//return the distance to the serial monitor
Serial.println(distance);
}
if(controlcode==2)
{
flashEyes();
}
}
}
void flashEyes()
{
//flash 50 times
for(int i=0;i<50;i++)
{
//turn the eyes on
digitalWrite(ledPin, HIGH);
//wait for 150ms
delay(150);
//turn the eyes off
digitalWrite(ledPin, LOW);
//wait for 100ms
delay(100);
}
}
Setting up the Raspberry Pi
My Raspberry Pi 2 Model B is connected to the Arduino over a standard USB A/B cable, and it's also connected to a Bluetooth speaker mounted behind the spider. To run things, I have a Python script that sends a serial command to the Arduino to measure the distance every 50 milliseconds. If the Arduino returns a value between 20 and 120 centimeters, the Python script sends a serial command to the Arduino to flash the LEDs, and uses mpg123 to play a random MP3 file.
Here's the script:
import serial
import time
import subprocess
import random
port = "/dev/ttyACM0"
def playsound():
i = 0
screamnumber = str(random.randint(1,5))
screamfile = "/home/pi/halloween/X.mp3".replace("X",screamnumber)
subprocess.Popen(["mpg123", screamfile])
if __name__ == '__main__':
s1 = serial.Serial(
port=port,\
baudrate=9600)
s1.flushInput()
#wait 5 seconds before telling the arduino look for motion
time.sleep(5)
try:
while True:
if s1.in_waiting==0:
#print('sent request')
s1.write('1\n')
time.sleep(.05)
if s1.in_waiting>0:
#print('received response')
inputValue = s1.readline()
#print('Distance: ' + inputValue)
if int(inputValue) > 20 and int(inputValue) < 120:
print('Distance: ' + inputValue)
s1.write('2\n')
playsound()
time.sleep(15)
else:
time.sleep(.05)
#quit with ctrl + c
except KeyboardInterrupt:
print("script stopped by user")
To enable serial communication, I am using the pySerial module. I should also note the logic for playing a random MP3 assumes that there are five files in the same directory as the script that are named 1.mp3, 2.mp3, etc. Using a different number of files would require changing the upper bound value on line 9.
Happy Halloween!