A home intrusion detection setup (sort of)
And Yet It Sees
The second thing I had to deal with was vision. I could have used vision literally in my project by hooking up a camera and using the Motion [3] program to check differences between one image and the next, because I had a camera I could have taken apart. (I did eventually use it, but not for that purpose, as you will see later in Figure 7.) The problem is that I had an ultrasonic sensor, and I wanted to play with it.
The sensor is an old SeeedStudio Ultrasonic Sensor V1.0, as shown in Figure 6. Originally, I thought I would have to break out my trusty Digispark [4], assuming the sensor would deliver the distance as an analogical number back over the wire with the distance in centimeters, microns, or whatever.
Not so. An ultrasonic sensor is much more basic than that, and your software has to do the heavy lifting. It calculates the distance in the same way you would a thunderstorm: You send a pulse over the pin to the sensor that makes the speaker emit a very short ultrasonic blast. Then, you start counting.
When the microphone picks up the echo, you can calculate the distance to the nearest object, taking into account the speed of sound. Sound travels at about 34,000cm/sec. If the sensor captures the echo, say, 0.001 seconds after it was sent, the pulse has traveled 34 centimeters. But, the ultrasonic pulse travels to the object and then bounces back, so you have to divide that figure by two to get the real distance to the object: 17cm.
Many newer ultrasonic sensors have four pins: one for power, one for ground, and then an output pin that receives the pulse from one of the Pi's GPIO pins and an input pin to capture the echo and send a HIGH
signal to the Pi when it arrives. This allows you to set up the two GPIOs – one as output and another as input – at the beginning of your program, so you don't have to faff about with that later when timing is of the essence.
But, not my V1.0! The V1.0 only has three pins: The sensor pin is used for input and output, so you have to set your GPIO to output, send the pulse out, switch the pin to input, start counting, and then wait for the echo. If you're not careful about the order in which you do all of the above, you'll find that the pulse has come and gone before you start listening, and your program will loop forever waiting for a sonic pulse that's already history.
After a lot of trial and error, I managed to get things to work with a program like you can see in Listing 3.
Listing 3
sonar.py
01 import RPIO, time 02 03 sonic_pin=17 04 stop=time.time() 05 06 # Shoot pulse 07 RPIO.setup(sonic_pin, RPIO.OUT, initial=RPIO.LOW) 08 time.sleep(0.5) 09 RPIO.output(sonic_pin, True) 10 time.sleep(0.00001) 11 RPIO.output(sonic_pin, False) 12 13 # Capture pulse 14 RPIO.setup(sonic_pin, RPIO.IN) 15 16 while RPIO.input(sonic_pin)==0: 17 start = time.time() 18 19 while RPIO.input(sonic_pin)==1: 20 stop = time.time() 21 22 # Calculate pulse length 23 elapsed = stop-start 24 25 # Calculate distance in cms. 26 distance = elapsed * 17000 27 28 print distance 29 30 # Reset GPIO settings 31 RPIO.cleanup()
If you run the script with
sudo python sonar.py
it will print out the distance to the nearest object in centimeters, which is much cooler than using a tape measure.
Bear in mind that this program works for a sensor that has its input and output going through the same pin. If you have a more modern sensor with four pins, you will be able to separate the input and output onto two separate pins.
Mounting the ultrasonic sensor proved a challenge. I ended up carving a piece of cardboard from a box, screwing it to the plaque, and then sticking the sensor to the cardboard with Blu-Tack. You can see the result of my handiwork in Figure 7.
All Together Now!
The next step is making the servo and ultrasonic sensor work together. I decided that my turret would have two modes: scanning and surveillance. First, it would scan the environment, calculating the distance to objects in its 180º arch. That's what's going on in lines 25 to 36 in Listing 4. In the setup()
function block of code (lines 12-23), you call the scan()
function three times (I'll explain why later) and rotate the turret first counterclockwise, then clockwise, and finally counterclockwise again, measuring the distance in each position as you go.
Listing 4
surveillance.py
001 #!/usr/bin/env python 002 003 import RPIO.PWM as PWM 004 import RPIO, time, random, os, shutil 005 import pygame.camera 006 from PIL import Image 007 008 import pygame.image 009 pygame.camera.init() 010 cam = pygame.camera.Camera(pygame.camera.list_cameras()[0]) 011 012 def setup(start, end, resolution): 013 014 distance={} 015 016 d1=scan(start, end+1, resolution) 017 d2=scan(end, start-1, -resolution) 018 d3=scan(start, end+1, resolution) 019 020 for i in d1.keys(): 021 distance[i]=int((d1[i]+d2[i]+d3[i])/3) 022 023 return distance 024 025 def scan(start, end, step): 026 servo = PWM.Servo() 027 servo_pin=2 028 distances={} 029 030 for pos in range (start, end, step): 031 servo.set_servo(servo_pin, pos) 032 distances[pos]=getDistance() 033 034 servo.stop_servo(2) 035 036 return distances 037 038 def moveTo(pos): 039 servo = PWM.Servo() 040 041 servo_pin=2 042 servo.set_servo(servo_pin, pos) 043 return int(getDistance()) 044 045 servo.stop_servo(2) 046 047 def getDistance(): 048 RPIO.cleanup() 049 050 sonic_pin=17 051 stop=time.time() 052 053 # Shoot pulse 054 RPIO.setup(sonic_pin, RPIO.OUT, initial=RPIO.LOW) 055 time.sleep(0.5) 056 RPIO.output(sonic_pin, True) 057 time.sleep(0.00001) 058 RPIO.output(sonic_pin, False) 059 060 # Capture pulse 061 RPIO.setup(sonic_pin, RPIO.IN) 062 063 while RPIO.input(sonic_pin)==0: 064 start = time.time() 065 066 while RPIO.input(sonic_pin)==1: 067 stop = time.time() 068 069 # Calculate pulse length 070 elapsed = stop-start 071 072 # Get the distance in cms. 073 distance = elapsed * 17000 074 075 076 # Reset GPIO settings 077 RPIO.cleanup() 078 079 return distance 080 081 def startCam (): 082 global cam 083 cam.start() 084 085 def stopCam (): 086 pygame.camera.quit() 087 088 def takePic(): 089 global cam 090 img = cam.get_image() 091 pygame.image.save(img, "/tmp/photo.bmp") 092 093 im=Image.open("/tmp/photo.bmp") 094 newImName=time.strftime("%y_%m_%d_%H_%M_%S") + ".png" 095 im.save("/tmp/" + newImName) 096 097 shutil.copy("/tmp/" + newImName, "/home/pi/.secretdirectory/" + newImName) 098 099 if __name__ == "__main__": 100 101 # Spin around and get 102 distance=setup(550, 2450, 100) 103 104 # Start watching 105 startCam() 106 107 while True: 108 pos = random.choice(distance.keys()) 109 watched = moveTo(pos) 110 if (watched < distance[pos] - 10) or (watched > distance[pos] + 10): 111 print "Something changed at %s. Original: %s, New: %s " % (pos, distance[pos], watched) 112 # Take photo 113 takePic() 114 distance[pos] = watched 115 116 stopCam()
Real-world data is notably imprecise. If the servo is not exactly in the right place when taking the measurement, the measurement will be wrong. The solution is to measure distances for each position three times, just to make sure (lines 16-18) and then average them out (line 21). The final list of measurements, along with the position at which they were taken indexed as keys, are stored in the Python dictionary construct distance
.
When you're done scanning, the turret goes into surveillance mode (lines 107-114). In surveillance mode, it rotates to random positions (line 108) and checks to see whether anything has moved from its original position (line 110). If a sizable difference exists in the measurement (more than 10cm), the program takes a photo with the webcam, replaces the original value in the dictionary, and continues watching.
« Previous 1 2 3 4 Next »
Buy this article as PDF
Pages: 8
(incl. VAT)