Building a Smart Home Security System With Arduino, Raspberry Pi, and Machine Learning
by nomiedi84 in Circuits > Raspberry Pi
30 Views, 2 Favorites, 0 Comments
Building a Smart Home Security System With Arduino, Raspberry Pi, and Machine Learning
Home security systems have always had the same two problems: either they need someone watching a screen at all times, or they go off for no reason and everyone starts ignoring them. Commercial products like Ring or Nest solve some of this, but they need a stable internet connection and a monthly subscription to work properly. For a lot of people, that is not a realistic option.
This project takes a different approach. By combining an Arduino with a Raspberry Pi and a lightweight machine learning model, the goal was to build a security system that processes everything locally, sends an email alert only when an actual person is detected, and keeps running even when things go wrong. The whole thing runs on hardware that costs a fraction of a commercial system and does not depend on any external service to function.
The system splits the work between two devices on purpose. Arduino handles the motion sensing side and Raspberry Pi handles everything that needs computing power. This separation is what makes the whole thing reliable.
When the PIR sensor picks up movement, Arduino reacts in microseconds and immediately triggers the buzzer. At the same time it sends a signal over USB to the Raspberry Pi, which then wakes up, takes a series of photos, runs each one through a machine learning model to check whether a person is actually there, and sends an email alert with the best photo attached if enough photos confirm a presence. A Flask web dashboard running on the Pi shows the current system status and the last captured image, accessible from any device on the same network.
The reason both devices are needed, rather than just using the Pi for everything, comes down to one practical problem. The Pi runs Linux, which means the operating system constantly interrupts running processes to manage background tasks. Under real load, those interruptions cause gaps of up to 350 milliseconds in sensor monitoring, which is long enough to completely miss a detection event. Arduino has no operating system at all. It responded to every sensor signal in 3 to 4 microseconds with zero variation across every single test. That difference is not a minor detail, it is the reason the design works.
video link: https://youtu.be/YDek1pKt_kY
This project was submitted to Deakin University, School of IT, Unit SIT210/730 — Embedded Systems Development.
Supplies
The full list of hardware used in this project is below. Nothing here is specialised or hard to find, all of it is available from standard electronics suppliers.
Hardware
Raspberry Pi 4 (2GB or higher recommended) — the main processing unit that runs the machine learning model, web dashboard, and email system
Arduino Uno — handles real time motion sensing and local alerts
Raspberry Pi Camera Module v2 — captures photos when motion is detected
PIR Motion Sensor (HC-SR501) — detects movement in the room
LED (any colour) — gives an immediate visible alert on detection
Buzzer (active or passive) — gives an immediate audio alert on detection
10kΩ resistor — used as a pull-down on the PIR sensor pin to prevent false triggers
USB Type-B cable — connects Arduino to the Raspberry Pi and also powers the Arduino directly
10,000mAh 30W power bank — used as a manual backup power source
Jumper wires and a small breadboard — for connecting components
A physical enclosure or case — to house everything together
Software
Raspberry Pi OS (latest version)
Arduino IDE — for uploading code to the Arduino
Python 3 with the following libraries: pyserial, Pillow, opencv-python-headless, flask, RPi.GPIO
MobileNet SSD pretrained model files (model.prototxt and model.caffemodel)
Gmail account with an app password configured for SMTP email sending
Wiring & Hardware Setup
The wiring is straightforward and all fits on a small breadboard. The PIR sensor has three pins: VCC goes to Arduino 5V, GND goes to GND, and the signal pin goes to Arduino Pin 2. A 10kΩ resistor is connected between Pin 2 and GND as a pull-down, so the pin reads LOW when the sensor is idle rather than floating randomly and causing false triggers. The LED connects to Pin 3 with a current limiting resistor to GND. The buzzer connects to Pin 4 and GND.
On the Raspberry Pi side, the Camera Module v2 connects via ribbon cable to the camera port on the Pi, with the blue side of the connector facing toward the HDMI ports. The Arduino connects to the Pi through a standard USB Type-B cable, which also powers the Arduino directly so no separate power supply is needed for it.
Once everything is wired, all components go into the enclosure. The front of the case has a hole for the PIR sensor and camera to see through, and the back has holes for the power cable and USB cable.
Software Setup
Everything below runs on the Raspberry Pi over SSH. Open a terminal on another machine and connect first: ssh <username>@<ip_address>
Update the system before doing anything else:sudo apt update && sudo apt upgrade -y
Create the project folder and a virtual environment to keep dependencies clean:
mkdir ~/smart_security
cd ~/smart_security
python3 -m venv venv
source venv/bin/activate
Install the required Python libraries:
pip install pyserial pillow opencv-python-headless flask RPi.GPIO
The Camera Module v2 on newer versions of Raspberry Pi OS conflicts with PipeWire, which blocks camera access. Disable it permanently with these commands:
systemctl --user stop pipewire wireplumber
systemctl --user disable pipewire wireplumber
systemctl --user mask pipewire wireplumber
systemctl --user mask pipewire.socket
systemctl --user stop pipewire.socket
Test the camera to confirm it is working:
rpicam-still -o test.jpg --nopreview --vflip --hflip
ls -lh test.jpg
The vflip and hflip flags are needed because the camera sits upside down inside the enclosure due to how the ribbon cable is routed. If the test produces a file, the camera is working.
Download the MobileNet SSD model files that the ML detection runs on:
wget https://raw.githubusercontent.com/chuanqi305/MobileNet-SSD/master/voc/MobileNetSSD_deploy.prototxt -O model.prototxt
wget https://github.com/nikmart/pi-object-detection/raw/master/MobileNetSSD_deploy.caffemodel -O model.caffemodel
Upload the Arduino code using Arduino IDE on a laptop, then confirm the Pi can see the Arduino on the USB port:
ls /dev/ttyACM*
Open main.py and update the Gmail credentials near the top of the file:
GMAIL = "your_gmail@gmail.com"
APP_PASSWORD = "your 16 character app password"
The app password comes from Google Account settings under Security, not the regular Gmail password. Two-factor authentication needs to be enabled on the account before Google will allow generating one.
Run the system:
python3 main.py
Open the dashboard in a browser on any device on the same network:
http://<raspberry_pi_ip>:5000
To make the system start automatically every time the Pi boots, register it as a systemd service:
sudo nano /etc/systemd/system/security.service
sudo systemctl daemon-reload
sudo systemctl enable security.service
sudo systemctl start security.service
sudo systemctl status security.service
Code
The full source code is available on GitHub at https://github.com/ElinaEdraki/SIT210.git. The explanation below walks through the key parts.
Arduino
The Arduino sketch is short by design. It reads the PIR sensor every second and sends either MOTION or CLEAR over the serial port depending on what the sensor reports. The LED and buzzer respond immediately when motion is detected without waiting for anything from the Pi.
void loop() {
motionState = digitalRead(pirPin);
if (motionState == HIGH) {
digitalWrite(ledPin, HIGH);
tone(buzzerPin, 1000);
Serial.println("MOTION");
delay(1000);
noTone(buzzerPin);
} else {
digitalWrite(ledPin, LOW);
noTone(buzzerPin);
Serial.println("CLEAR");
delay(1000);
}
}
Three-Thread Structure
The Python script runs three threads in parallel so no single task can block the others.
Thread 1 listens to the Arduino serial port continuously. When a MOTION signal arrives it places an item in a shared queue and moves on immediately. It also tracks how long since the last signal and sends a warning email if nothing arrives for 60 seconds, which likely means the PIR sensor has been disconnected.
line = ser.readline().decode('utf-8').strip()
if line == "MOTION":
if alert_queue.empty():
alert_queue.put("MOTION")
Thread 2 picks up items from the queue and runs the presence detection process. It takes 7 photos over approximately 60 seconds. The first two photos act as early exit checks — if no person is detected in either of them the system stops immediately rather than spending the full minute processing. From photo 3 onwards all remaining photos are taken regardless. If 5 or more out of 7 photos detect a person with confidence above 50%, an email alert is sent with the best photo attached.
person_found, confidence = detect_person(jpg_path)
if person_found and confidence > 50:
photos_with_person += 1
if confidence > best_confidence:
best_confidence = confidence
best_photo = jpg_path
Thread 3 runs quietly in the background and checks every 60 seconds whether any alerts failed to send due to a network issue. Failed alerts are saved to a text file and retried when connectivity returns.
ML Detection
The MobileNet SSD model runs through OpenCV's DNN module. Each photo is resized to 300x300, converted to a blob, and passed through the network. The model returns confidence scores for each detected object. Class index 15 is the person class. Any detection above 50% confidence counts as a confirmed person.
blob = cv2.dnn.blobFromImage(
cv2.resize(image, (300, 300)), 0.007843, (300, 300), 127.5)
net.setInput(blob)
detections = net.forward()
for i in range(detections.shape[2]):
confidence = detections[0, 0, i, 2]
idx = int(detections[0, 0, i, 1])
if idx == 15 and confidence > CONFIDENCE:
return True, confidence * 100
Testing
Testing
Testing was done in stages rather than all at once. Each component was confirmed working on its own before connecting everything together, which made it much easier to find problems early without having to guess which part was causing the issue.
Serial Communication
The first thing to test was whether the Pi was receiving signals from Arduino correctly. A simple script was written to listen on the USB serial port and print every message that arrived. Waving a hand in front of the PIR sensor confirmed that MOTION and CLEAR signals were coming through at 9600 baud without any issues.
Camera
Once serial communication was confirmed, the camera was tested next. A short script was written to take a photo automatically when a MOTION signal arrived. The photo was copied to a laptop and checked visually. The camera turned out to be mounted upside down because of how the ribbon cable sat inside the enclosure, so the vflip and hflip flags were added to fix the orientation automatically in every camera command from that point on.
ML Detection
A separate test script was written to run the MobileNet SSD model on a saved photo and print the confidence percentage. Tests were done at different distances and under different lighting conditions. Confidence results ranged from 63% to 98%, with the best results in well-lit conditions at around 1 to 2 metres from the camera. Low light dropped confidence noticeably but detections still came through above the 50% threshold in most cases.
The email system was tested by sending a test message with a photo attached through Gmail SMTP. The email arrived correctly with the photo, which confirmed the app password configuration was working. A separate test confirmed that failed emails were saved to pending_alerts.txt and successfully retried once the network was restored.
Full System
After all individual parts were confirmed working, the full system was tested together. Several scenarios were checked one by one.
Normal operation confirmed that triggering the PIR sensor caused Arduino to activate the LED and buzzer immediately, followed by 7 photos being taken over roughly 60 seconds and an email sent when a person was confirmed in 5 or more of them.
The early stop scenario confirmed that a quick walkby with no sustained presence caused no alert, because no person was detected in the first two photos and the system stopped processing early as intended.
The fault tolerance scenarios were tested by physically unplugging the Arduino USB cable mid-run, which confirmed the auto-reconnect worked within 5 seconds. Disabling WiFi confirmed that alerts were saved locally and retried successfully when the connection came back. Pressing Ctrl+C confirmed that a shutdown warning email was sent before the system stopped.
Benchmark Tests
Two benchmark tests were run specifically to confirm the two-board design was the right call. The first test measured Raspberry Pi GPIO polling response times under real system load with ML inference, Flask, and email all running at the same time. Regular monitoring gaps of up to 350 milliseconds were recorded due to OS task scheduling, which would cause missed detections in a real scenario.
The second test measured Arduino hardware interrupt response times under the exact same conditions. Arduino responded to every signal in 3 to 4 microseconds with zero variation and zero gaps across the entire test period.
Arduino hardware interrupts were between 12,500 and 87,500 times faster than Raspberry Pi GPIO polling depending on system load at the time. That result confirmed the two-board architecture was a practical necessity rather than just a design preference.
Result
The system met every requirement that was set at the start. Motion detection response from the Arduino side was consistently in the 3 to 4 microsecond range across all tests, which is well within the real-time requirement. Total time from motion detected to email sent averaged around 6 seconds, comfortably under the 10 second target that was set at the beginning of the project.
The ML model performed reliably in normal indoor conditions. Confidence scores ranged from 63% to 98% depending on distance and lighting, with the best results at 1 to 2 metres in a well-lit room. The 5 out of 7 photo threshold proved to be a good balance between accuracy and avoiding false alerts. Quick walkthroughs triggered no alerts in any of the tests, which was one of the main goals from the start.
All fault tolerance scenarios passed. Arduino disconnection and reconnection worked automatically within 5 seconds every time. The PIR timeout warning fired correctly after 60 seconds of no signal. Failed emails were saved and retried successfully once the network came back. The shutdown warning email arrived correctly in every test.
The benchmark numbers were the most interesting result overall. Going into the project the assumption was that the Pi could probably handle everything on its own with careful coding. The test results made it clear that OS scheduling is not something that can be worked around in software. The gaps were real, they were consistent, and they were large enough to matter in a real security scenario. Having actual numbers to point to made the two-board decision feel justified rather than just a guess.