Have you ever wondered who is coming through your cat flap when you are not around?
Well I hadn’t, until I walked into my utility room one day and found a big fat grey moggy half in and half out of the cat flap looking up at me. This was not my cat. It was a tense moment as we both stared at each other. The offending moggy slowly backed up and stared at me defiantly from outside the cat flap.
This got me thinking about setting up a Raspberry Pi powered cat detector. I wanted to know if this cat was coming in regularly and helping itself to my cat’s food.
I sat down and planned out my basic design goals for my Raspberry-Pi-Cat-Detector (or RPi-CD for short):
1. Detect when a cat enters or exists the cat flap.
2. Take a photo of the cat
3. Email the photo to me
I like to use Python for scripting my projects mainly because it is easy, usually quite simple to understand and there is a lot of support on the internet when I need help (which is often!). There are also lots of modules (e.g. in this project I have used GPIO and picamera) that have been written by the Python community that let you do lots of really cool things with minimal coding.
Step 1. How to detect if something is moving through the cat flap
The first problem to solve was how to know if a cat is coming in through the cat flap. There are lots of ways of doing this. I chose to use a HC-SR501 PIR Infrared Motion Sensor Module (learn about them here) connected via my Raspberry Pi’s GPIO pins. This was mainly because they are cheap and fairly reliable which is presumably why they are used in many home security systems.
So how to connect the PIR to my Raspberry Pi?
All Raspberry Pi’s have what are called GPIO pins. They stick up out of the Pi board and look like this:
I am using a Raspberry Pi Model B version 2. If you are using a different version (e.g. Raspberry Pi model B or B+) you might need to change which pins you are using. If you are new to GPIO check out this website: http://pi.gadgetoid.com/pinout
PIR sensors can vary as to which order the pins are in. But they will all have 3. And they will be:
- VCC (positive)
- GND (negative)
- OUT (signal – HIGH or LOW)
Your task is to connect up these 3 PIR pins to your Raspberry Pi via 3 GPIO pins. There are lots to chose from and a word of warning, if you get this bit wrong, you can fry your Raspberry Pi for good. Make sure you check and double check!
I am using physical pin numbers here and not BCM (see here for more on this).
VCC goes to GPIO pin 2
GND goes to GPIO pin 6
OUT goes to GPIO pin 11
I used a bread board as a way of making this easier, and as a way of connecting up an LED to use as a way of showing when a cat has been detected.
Python has a module called GPIO (or RPi.GPIO see here) which will let me find out whether the PIR OUT pin, connected to GPIO pin 11 is HIGH or LOW. Sounds complicated right? But it really isn’t so bad when you have wired it all up, I promise.
When the PIR OUT is High (5v in this case), it means that the PIR has detected movement. LOW (0v) means it has not detected movement.
Using the GPIO module we can keep monitoring the status of the PIR OUT pin via the RPi.GPIO module and our Raspberry Pi GPIO pin 11.
The Python module RPi.GPIO is already isntalled if you have an up to date copy of NOOBS (for more on NOOBs see here).
Now we have a way of allowing our Raspberry Pi Cat Detector to be able to detect movement. But we are still blind!
Here is the code I used (cat_io.py):
GPIO.setmode(GPIO.BOARD) # no warnings GPIO.setwarnings(False) # choose pins for reading PIR OUT and lighting up LED PIR_PIN = 11 LED_PIN = 13 class PIR(object): def __init__(self): # set PIR_PIN as input - so we can read if HIGH or LOW GPIO.setup(PIR_PIN, GPIO.IN) def read(self): # tiny function to read PIR OUT on pin 11 return GPIO.input(PIR_PIN) class LED(object): def __init__(self): # set LED_PIN as output - so we can set as HIGH (on) or LOW (off) GPIO.setup(LED_PIN, GPIO.OUT) def on(self): # turn LED on GPIO.output(LED_PIN, 1) def off(self): # turn LED off GPIO.output(LED_PIN, 0) def blink(self, on_seconds, off_seconds, total_blinks): # blink LED for i in range(total_blinks): self.on() time.sleep(on_seconds) self.off() time.sleep(off_seconds)
Step 2. How to capture a photo of the detected cat
So to take a picture, whenever there was movement at the cat flap, I needed a camera. Luckily I had a Pi NOIR (see here) already. This is like a normal camera module but without an infrared filter. You can just use a normal pi camera, I was too cheap to buy another one and was willing to put up with pictures with no infrared filster (the main difference is the photos come out slightly more pinkish than normal).
The Python module picamera (see here) is a great way of taking pictures using Python. I was able to detect motion at the cat flap, now I could take a picture when I had detected that motion.
Here is the code I used (cat_camera.py):
import time from datetime import datetime as dt import picamera from os import walk class my_camera(object): def take_picture(self, save_location): ''' when called, takes a picture and svaes jpg in specified location WARNING - does not check if file already exists! ''' with picamera.PiCamera() as camera: camera.resolution = (1024, 768) file_name = self.create_file_name() camera.capture(save_location + '/' + file_name) print 'taking picture' def create_file_name(self): return dt.now().strftime("%Y_%m_%d_%H_%M_%S") + ".jpg"
Step 3. How to send an email with the photos attached
The final hurdle was figuring out how to send an email with an attached photo, when I had detected and photographed a cat.
This turned out to be quite easy using modules that come as standard with Python. All I needed was a gmail account to use for this project and since gmail accounts are free, I just set up a new account.
Here is the code I used (cat_email.py):
import smtplib from datetime import datetime as dt from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.image import MIMEImage from os import listdir class send_mail(object): def send(self, file_location): ''' who you want to send your cat detector emails to ''' email_to = "this is where you put the to email address" ''' and who they are from e.g. the new gmail account you just set up ''' email_from = "this where you put the sender email address" # gmail smtp stuff smtp_server = 'smtp.gmail.com' smtp_port = 587 smtp_user = email_from ‘’’ enter your gmail password here - warning this is visible to anyone who can open this file!!’’’ smtp_pass = ‘youremailpasswordhere’ # create message msg = MIMEMultipart('alternative') msg['To'] = email_to msg['From'] = email_from # get list of attachments i.e. all photos within subdirectory ‘’’ I used a directory to store photos in and made it so that photos taken within 10 seconds of each other were grouped into the same subdirectory ’’’ attachment_list = self.get_attachment_list(file_location) if len(attachment_list) > 0: count = 0 # here I make sure the max number of attached photos is 3 while count < len(attachment_list) and count < 3: pic = attachment_list[count] file_name = file_location + '/' + pic image_data = open(file_name, 'rb').read() image = MIMEImage(image_data, name=pic) msg.attach(image) count += 1 time_now = dt.now().strftime('%H:%M:%S') # create email subject msg['Subject'] = 'Cat Detector: cat detected (%s)' % time_now # email body body = '''Hi Tim,\nA cat has just been detected, see attched picture(s). \nBest,\nCatDetector ''' body = MIMEText(body, 'plain') # attach body msg.attach(body) # try and send email try: s = smtplib.SMTP(smtp_server, smtp_port) s.ehlo() s.starttls() s.login(smtp_user, smtp_pass) s.sendmail(email_from, email_to, msg.as_string()) print 'email sent' except: print 'error sending mail' def get_attachment_list(self, file_location): # use listdir to return all files in directory return listdir(file_location)
Bringing it all together!
So if you have made it this far – well done!
The next thing I needed was a way to create and delete directories. And to reset the main folder with all the photos in. I added this to make sure the disk space was reclaimed each time I resey my pi. If you want to keep all your photos, you will need to tweak this code.
And yes be careful with this code. You have the power to delete things!
import os import shutil class cat_dirs(object): def __init__(self): # get file path of parent directory self.cat_parent_path = os.path.abspath('.') + '/' def create_dir(self, episode): try: ''' creates a directory within temp_pic directory ''' directory = self.create_dir_name(episode) os.makedirs(directory) except: directory = None print 'error making directory' return directory def create_dir_name(self, episode): # create dir path/name from parent path and episode return self.cat_parent_path + 'temp_pic/' + episode def delete_dir(self, dir_name): ''' deletes a directory and contents within temp_pic directory ''' directory = self.cat_parent_path + 'temp_pic/' + str(dir_name) try: shutil.rmtree(directory) except: print 'error deleteing directory' def reset(self): ''' delete temp_pic and recreate ''' directory = self.cat_parent_path + 'temp_pic/' try: shutil.rmtree(directory) os.makedirs(directory) except: print 'error reseting directory'
The last part – writing a main loop to run all the code. (if you code a lot you will probably have guessed that as I wrote this code I created all the other classes shown above as I went along – but that is not so easy to follow in a blog article).
[editors note: this code is quite long and might take a bit of time to follow. You might find it just as easy to write your own main loop 😀 ]
#!/usr/bin/env python import cat_camera import cat_io import cat_os import cat_email import time class cat_detector(object): def __init__(self): '''instantiate cat classes: camera, PIR, LED, cat_dirs and send_mail''' self.cat_cam = cat_camera.my_camera() self.PIR = cat_io.PIR() self.LED = cat_io.LED() self.cat_dirs = cat_os.cat_dirs() self.send_email = cat_email.send_mail() #reset directory containing photos self.cat_dirs.reset() def get_status(self): return self.PIR.read() def main(self): # initiate variables cycle = 0 detection = 0 time_since_last_detection = 0 episode = 0 high_alert = False '''turn on LED to show script is running useful if running headless on boot (i.e. no monitor)''' self.LED.on() time.sleep(10) self.LED.off() #main loop while True: '''check if a cat is detected, if it is, add one to detection count and if start of episode, go into HIGH ALERT''' if self.get_status(): high_alert = True ''' add one to detection because this is a new cat detection ''' detection += 1 ''' if this is the first detection of an episode, add one to episodes ''' if detection == 1: episode += 1 # first detection of new episode so create a new directory if episode < 10: folder_name = '0' + str(episode) else: folder_name = str(episode) current_dir_name = self.cat_dirs.create_dir(folder_name) # take picture of detected cat self.LED.on() self.cat_cam.take_picture(current_dir_name) self.LED.off() '''because there has been a new detection, reset time since last detectionto 0''' time_since_last_detection = 0 # indicate detection with LED blinking self.LED.blink(0.05,0.05,25) else: # else if there is no detection if high_alert: ''' if high alert == true check time since last detection, if too long, return alert status to normal ''' if time_since_last_detection > 9: high_alert = False time_since_last_detection = 0 detection = 0 self.LED.on() # shows user email is being sent self.send_email.send(current_dir_name) self.LED.off() else: # else add another second to time since last detection time_since_last_detection += 1 print '\n' *50 + '* ' * 30 print '''\nCycle: %s, \nAlert Status: %s, \nEpisode: %s, \nDetection: %s \nTime sinse last detection: %s''' % (cycle, high_alert, episode, detection, time_since_last_detection) print '\n' cycle += 1 time.sleep(1) # run the cat detector! RPiCD = cat_detector() RPiCD.main()
Being really clever
Now by this point you can run the main loop and see if your cat detector is working. You might have used my code or written your own. But the clever bit is making this script run when you boot up your Raspberry Pi without connecting your monitor, mouse or keyboard. I used the LED in my main loop to show me that the script was running at the start (a long flash at the start) and then when my RPi CD was taking a photo or sending an email. This made life a lot easier than having to guess if everything was working ok.
A good guide for doing this is here:
Did I capture the cat?
So did building this cat detector work? Did I find out if the big grey moggy was pilfering my cats food?
I detected my cat:
and a few other things:
Was this the invading cat coming in by stealth at night?
So what is next for the cat detector?
Night time picture taking needs improving, as the above picture shows. How could this be done?
a) using an infra-red LED to light up the picture without alerting the cat (my PiNOIR would be perfect for this as it has not infra red filter) and
b) experimenting with picamera and the exposure time (longer exposure would mean lighter picture but if the cat is moving fast, it could lead to blurred pictures)
[please note – the code given in this article may need reformating in IDLE or similar code editor because Python is sensitive to white space e.g. tabs, which can be dissrupted when posting code into wordpress. No responsibility can be taken by the author for attempts at recreating the RPi-Cat-Camera. The code is given for illustrative purposes only.]