Trapped Cat Detector
This project is designed around an issue in my household that I am sure is all too common among pet owners, especially if the pet is small- that being, in this case, that my cat frequently gets trapped in the rooms she is not allowed to enter. This specific setup focuses on a large, double-door room in my basement that my cat will dart into and hide in whenever the door is open, and often become trapped in when the door is closed. We try to keep the door closed, but often have no idea if the cat is in the room when closing it. Using a microcontroller, a motion sensor and a cheap laser diode and photocell tripwire setup, this project activates a motion sensor in the room whenever the door is closed, sending motion data to a spreadsheet if movement is detected near the door, meaning the cat is trapped.
Supplies
For the detection system, these are the 4 primary components:
- Particle Argon (Any microcontroller that can supply 5 volts to the PIR sensor will work, but this project was designed with particle's Webhook functionality in mind)
- Photocell/Photoresistor
- IR Laser Diode
- PIR Motion Sensor
For the circuit, we will also need:
- 220 ohm Resistor x 2
- 1000 ohm Resistor
- LED x 2
- M2F Jumper Wires x 3
- M2M Jumper Wires and Breadboard
- AA Batteries* x 2
I also used the following tools:
- Boxcutter
- Wire cutter
- Wire stripper
- Soldering Iron (Optional, for slideswitch)
*I used some 1-ply cardboard to build housing for the laser diode, and a small slide switch to save on battery life, but you can also connect the laser to a 5 volt wall wart or power supply or directly to the batteries.
Programming the Microcontroller
The following code is written in the C language, but is meant to be used with a Particle webhook integration. Any code that involves publishing data will need to be modified to fit other microcontrollers' specifications.
There is very little Particle-side code that is necessary for this project. Basically, we want the motion sensor to run only when the state of the door is closed. This occurs when the Laser Diode beam is able to hit the photoresistor, which is only possible if both doors are not obstructing the beam. The photoresistor will read multiple values depending on the lights in the room, so we want to make sure that the laser diode hits the photoresistor as directly as possible so we can set a threshold for closed-door detection.
Outside of setup(), we will declare the following variables:
const int beamThreshold = 1000;
int photoResistor = A0;
int motionDetector = 6;
int Signifier = 7;
int motionState = LOW;
int motionLight = 2;
int testButton = 4;
int buttonState = HIGH;
int prevState = LOW;
String data;
For now, let's set beamThreshold at something relatively low, like 1000. We can test the photocell's sensitivity later to determine the actual value.
The three variables from lines 8-10 are optional, but I recommend you implement buttons when we test the webhook's functionality.
Once we have those variables declared, we can set our inputs and outputs:
void setup() {
pinMode(motionDetector, INPUT);
pinMode(photoResistor, INPUT);
pinMode(testButton, INPUT_PULLUP);
pinMode(Signifier, OUTPUT);
pinMode(motionLight, OUTPUT);
Serial.begin(9600);
digitalWrite(Signifier, LOW);
}
Next, we'll set up the function that 'unlocks' the code in loop() so that it only runs when the beam is not intercepted.
bool doorIsClosed()
{
if (analogRead(photoResistor) >= beamThreshold)
{
return true;
}
else
{
return false;
}
}
Once we have that, we we can implement the button functionality for now, and have the motion sensor code on standby once we know the webhook is pushing to the spreadsheet.
This is the shell of loop(), and inside we can temporarily add the button state change detection:
void loop() {
if (doorIsClosed())
{
digitalWrite(Signifier, HIGH);
buttonState = digitalRead(testButton);
if (buttonState == HIGH && prevState == LOW)
{
Particle.publish("Maki Detected", "maki detected", PRIVATE);
digitalWrite(motionLight, HIGH);
}
prevState = digitalRead(testButton);
}
else
{
digitalWrite(Signifier, LOW);
digitalWrite(motionLight, LOW);
}
}
Inside Particle.publish(), you can replace the first and second parameters with the name of your webhook and the data you want to push to the spreadsheet. For now, we're just testing the webhook functionality, so it doesn't really matter.
It is imperative that we don't add delays anywhere in loop() to ensure that the argon is consistently checking the state of the door.
The motion sensor code is below. You can comment it out for now.
int pinState = digitalRead(motionDetector);
if (pinState == HIGH)
{
digitalWrite(motionLight, HIGH);
if (motionState == LOW)
{
data = "Motion begun";
Particle.publish("Maki Detected", data);
motionState = HIGH;
}
}
else
{
digitalWrite(motionLight, LOW);
if (motionState == HIGH)
{
data = "Motion ended"
Particle.publish("Maki Detected", data);
motionState = LOW;
}
}
Here, we're reading the state change of the motion sensor. The motion sensor outputs HIGH for a minimum of 3 seconds every time motion is detected, and then waits 5 more seconds where it won't detect motion. If it's set to repeat, it will continue to reset the 3 second timer each time motion is detected while it reads HIGH. In this case, we are more concerned with the initial state change every so often, as it is all that is necessary to prove the cat is trapped in the room, so we'll set the PIR to not repeat.
That's all for the Particle firmware. On to the webhook and spreadsheet integration, and then we can begin wiring and testing.
Pushing to Sheets
Now that the code is functional, we can test out pushing our motion data to google sheets. To do this, create a new sheet and name it "PIR Log".
In sheets, create 4 headers in the first row: "Date", "Time", "Device ID" and "Status".
Then, navigate to "Extensions" in the top left. We'll need the Apps Script extension installed to write the script the webhook will call.
Copy the following code into the Apps Script editor. You can run the test() to see if the sheet responds to the post request without having your argon send specific data to the sheet. If everything works, we'll move on to the webhook.
var dateStamp = "";
var timeStamp = "";
function test() {
var e = {};
e.parameter = {};
e.parameter.event = 'sheetTest1';
e.parameter.data = '[1,1234]';
e.parameter.coreid = '1f0030001647ffffffffffff';
e.parameter.published_at = new Date().toISOString();
doPost(e);
}
function doPost(e) {
//e.parameter.event
//e.parameter.data
//e.parameter.coreid
//e.parameter.published_at
var publishedAt = new Date(e.parameter.published_at);
dateStamp = dateStamp.concat((publishedAt.getMonth() + 1), "/", publishedAt.getDate(), "/", publishedAt.getFullYear());
timeStamp = timeStamp.concat(publishedAt.getHours(), ":", publishedAt.getMinutes(), ":", publishedAt.getSeconds());
/*var dataArray = [];
try {
dataArray = JSON.parse(e.parameter.data);
}
catch(e) {
}
*/
var sheet = SpreadsheetApp.getActiveSheet();
var row = [dateStamp, timeStamp, e.parameter.coreid, e.parameter.data];
// row = row.concat(dataArray);
sheet.appendRow(row);
var result = {};
result.ok = true;
return ContentService.createTextOutput(JSON.stringify(result))
.setMimeType(ContentService.MimeType.JSON);
}
Click the Deploy button in the upper righthand corner, and select "New Deployment". After saving your script, name it and click Deploy. Copy the URL for the web app at the bottom of the popup and close out.
Now we can create the webhook. Name the webhook whatever you made the first String parameter in Particle.publish(). Make sure it is set as a POST request, and is connected to your argon. Also ensure that in 'advanced', default fields are selected, so that Particle pushes your ID, date and data to the row properly.
That is all of the software (minus the commented out motion detector code) that we need, and we can now move on to wiring.
Wiring and Housing
Following this pinout diagram, connect all of your components to the breadboard and the corresponding pins on your Particle. The Laser Diode is separate from the argon and main breadboard as it requires a higher voltage than the microcontroller outputs. When setting up the PIR sensor, make sure the pin marked 5v is connected to VUSB on the argon and not the 3v3 pin. To extend the range of the PIR sensor, I used M2F jumper wires to connect the PIR pins to some alligator clips, and connected those to the breadboard and argon.
To stabilize the photocell in place, I cut a small slit 1.25" into about 2 square inches of cardboard and pushed the wires of the sensor into it, securing it at a fixed height. I then constructed a base for the housing and stuck adhesive wall mounting tape to the back of it. We will place the Photocell on one of the two double doors and the housing for the Laser Diode on the other, making sure the laser beam makes direct contact with the photocell when the doors are closed.
To construct housing for the Laser Diode, we'll create a battery pack that allows for removing the two AA batteries and recharging them. I used some solid core wire at the back of the pack to connect the positive and negative terminals of the batteries on that side when pressure is applied, and connected two separate wires at the front of the pack (these wires are not attached directly, only via pressure). The negative wire is soldered directly to the Laser Diode, and the positive wire is soldered to the far terminal of a slideswitch, with a different wire connected to the middle terminal completing the circuit at the Laser Diode when the slideswitch is pushed to the left. This step is not necessary if you are using a wall wart or 5v power supply, but it saves on battery life if you are using a battery pack.
When constructing housing for the AA batteries, make sure the Laser Diode rests 1.25" above the ground, which is the height the Photocell will be at. Once I had constructed the housing, I was able to turn on the Laser diode and align it directly in the path of the Photocell. I made sure the two made contact when I eventually mounted the cardboard housing to either door, and mounted the components when the door was fully shut, as shown in the photo.
Testing Components
First, we want to test the Photocell's sensitivity in the specific lighting of your room. To do this, flash the following code to the argon and aim the Laser Diode at the photocell.
int photoresistor = A0;
int signifier = 7;
int laserThreshold = 3400;
void setup() {
pinMode(photoresistor, INPUT);
Serial.begin(9600);
void loop() {
int resistorVal = analogRead(photoresistor);
Particle.publish(String(resistorVal));
if(resistorVal >= laserThreshold)
{
digitalWrite(signifier, HIGH);
}
else
{
digitalWrite(signifier, LOW);
}
delay(500);
}
Record the value printed to the Serial monitor in three states: lights on without laser, lights on and laser aimed, and lights off with laser aimed. Pick a value that is higher than the base 'lights on' value but lower than BOTH 'laser aimed' values. This will be your threshold, declared at
const int beamThreshold = 1000;
in our master program. For my room, a threshold of 2500 worked best.
Next, flash the updated master code and close the door so the photocell is hit by the laser. While the door is closed, if you press the button, you should see a new update to your spreadsheet. Once you're sure the button only works when the door is closed, you can comment out the button code and delete the comments on the motion sensor code. You should be able to tell if the motion sensor is working based on your motion LED, which should only turn on if you move in front of the PIR's dome when the door is closed. Check the spreadsheet one more time, and now the project should be ready to fully mount and test!
Completed Cat Detection System!
With all of our components in place, the system should be fully functional. It should detect the state of the door using the photoresistor and mounted laser diode no matter the room's lighting, and should send motion data to a spreadsheet should motion be detected in the closed off room, alerting you to open the door and free your beast from its cage. Above is a video demonstrating functionality of the motion sensor and door system, with the blue LED being 'motionLight' in our script.