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!