An IoT security-camera system built with a Raspberry Pi Pico W (C++ / FreeRTOS), a Raspberry Pi (Python / Flask / OpenCV), Firebase, and an Android app (Kotlin / Jetpack Compose).
The system performs real-time motion detection with instant mobile alerts and snapshots, filters false alarms with an on-device YOLOv8 person detector, serves a live MJPEG video stream, and lets the user arm/disarm the alarm and pan the camera remotely over MQTT.
- Pico W reads the PIR sensor and, if the system is armed, raises a signal line to the Pi.
- The Pi captures a frame from the camera.
- A YOLOv8n ONNX model runs on the Pi; the event is only propagated if a person is detected (false-alarm suppression).
- The annotated image is uploaded to Firebase Storage, and event metadata (timestamp + URL) is written to the Realtime Database.
- An FCM push notification is sent to the
alertstopic. - The Android app shows the notification, the live event feed, and the captured image.
- Arm / disarm the alarm from the app (
pico/alarm/set); the Pico publishes its state back onpico/alarm/status. - Pan the camera left/right from the app; commands travel app → MQTT broker → Pico W → servo (PWM).
- Flask server on the Pi exposes an MJPEG stream at
/stream. - The Android app renders it in a
WebViewon a dedicated screen, with pan controls underneath.
[ PIR Sensor ] ──▶ [ Pico W · C++ / FreeRTOS ]
│ agents: Wifi · Mqtt · Alarm · Beep · Servo
[ LED + Buzzer ] ◀───────┤
[ Servo / pan ] ◀───────┤
│ signal line (GPIO) MQTT (arm/status/pan)
▼ │
[ Camera ] ──▶ [ Raspberry Pi · Python ] ◀───── MQTT broker ─┤
│ · OpenCV frame capture │
│ · YOLOv8n ONNX person detection │
│ · Flask MJPEG live stream ─────────────┐ │
▼ │ │
[ Firebase ] │ │
· Storage (images) │ │
· Realtime DB (events) │ │
· Cloud Messaging (FCM) │ │
│ │ │
▼ ▼ ▼
[ Android app · Jetpack Compose ] ───────────────────┘
· FCM push + event feed + evidence viewer
· MJPEG live stream
· arm/disarm + camera pan (MQTT)
Detailed signal path for each of the three pipelines: triggering the alarm, controlling the servo, and watching in real time.
The firmware was refactored from a single bare-metal C loop into an
object-oriented, multitasking design. Each concern is an Agent (abstract
base class, Template Method pattern) created by an AgentFactory and run as
its own FreeRTOS task. Shared state is protected with a mutex; servo commands
are passed between tasks through a queue.
| Agent | Priority | Stack (words) | Responsibility |
|---|---|---|---|
WifiAgent |
idle+1 | 256 | Bring up CYW43 + lwIP, connect STA/WPA2, then start MqttAgent |
MqttAgent |
idle+2 | 1024 | Connect to broker, subscribe to control topics, toggle armed state, forward pan commands to the queue, publish status |
AlarmAgent |
idle+1 | 256 | Poll PIR; if armed, pulse the signal line to the Pi |
BeepAgent |
idle+1 | 256 | On notify input, drive LED + buzzer for ~2 s |
ServoAgent |
idle+1 | 256 | Consume pan commands from the queue, drive the servo via PWM |
Legacy C implementations are kept under
AlarmSystem/CTasks/for reference.
Default broker: broker.emqx.io:1883 (Pico), :8883 TLS (Android). Configured in
AlarmSystem/Tasks/AlarmShared.h and the Android MqttController / MainViewModel.
| Topic | Direction | Payload | Meaning |
|---|---|---|---|
pico/alarm/set |
app → Pico | any | Toggle armed / disarmed |
pico/alarm/status |
Pico → app | ON / OFF |
Current armed state (retained, last-will) |
pico/servo/left |
app → Pico | any | Pan camera left one step |
pico/servo/right |
app → Pico | any | Pan camera right one step |
pico/servo/up / pico/servo/down |
app → Pico | any | Reserved (subscribed, not yet handled) |
| Layer | Tech |
|---|---|
| Microcontroller | Raspberry Pi Pico W, Pico SDK 2.1.1, C++17, FreeRTOS (heap4) |
| MCU networking | pico_cyw43_arch_lwip_sys_freertos, lwIP MQTT client |
| Central processing | Raspberry Pi, Python 3 |
| Computer vision | OpenCV, YOLOv8n exported to ONNX, ONNX Runtime |
| Camera / streaming | Picamera2, Flask (MJPEG multipart/x-mixed-replace) |
| Cloud | Firebase Storage + Realtime Database |
| Notifications | Firebase Cloud Messaging (FCM) |
| Messaging | MQTT (broker.emqx.io) — lwIP client on Pico, HiveMQ client on Android |
| Mobile app | Android, Kotlin, Jetpack Compose, Navigation, Coil |
| Component | Description |
|---|---|
| Raspberry Pi Pico W | Runs the FreeRTOS firmware: PIR sampling, alarm signalling, Wi-Fi/MQTT, servo |
| Raspberry Pi | Frame capture, AI inference, cloud upload, Flask stream |
| PIR motion sensor | Digital HIGH/LOW on motion (heat) detection |
| LED + 220 Ω resistor | Visual alarm indicator |
| Active buzzer + S8050 transistor (+1 kΩ base resistor) | Audible alarm |
| Pi Camera | Image capture and live stream |
| Servo motor | Horizontal camera pan |
| Breadboard + jumper wires | Prototyping |
Pico W (AlarmSystem/Tasks/*.h):
| Signal | Pin | Constant |
|---|---|---|
| PIR input | GP17 | PIR_PIN |
| Signal out → Pi | GP1 | SIGNAL_PIN |
| Beep trigger input | GP2 | NOTIFY_PIN |
| LED | GP13 | LED_PIN |
| Buzzer | GP15 | ALARM_PIN |
| Servo (pan) | GP12 | SERVO_PIN1 |
Raspberry Pi (PI/config.py): motion signal input on BCM 26 (MOTION_PIN), pull-down, rising-edge triggered.
Install the toolchain:
sudo apt update
sudo apt install cmake gcc-arm-none-eabi build-essential gitGet the Pico SDK and the FreeRTOS kernel:
git clone https://github.com/raspberrypi/pico-sdk.git --branch master
git -C pico-sdk submodule update --init
export PICO_SDK_PATH=$PWD/pico-sdk
git clone https://github.com/FreeRTOS/FreeRTOS-Kernel.git
export FREERTOS_KERNEL_PATH=$PWD/FreeRTOS-KernelConfigure the broker/topics in AlarmSystem/Tasks/AlarmShared.h if you don't
want the public default, then build:
git clone https://github.com/milutin2002/alarmSecuritySystem.git
cd alarmSecuritySystem/AlarmSystem
mkdir build && cd build
cmake -DWIFI_SSID="wifi_name" -DWIFI_PASS="wifi_password" ..
makeFlash build/AlarmSystem.uf2 to the Pico: hold BOOTSEL, plug in USB, and
drop the file onto the RPI-RP2 drive.
The Wi-Fi country code is currently hard-coded to
CYW43_COUNTRY_SPAINinAlarmSystem/Tasks/WifiAgent.cpp— change it for your region.
System packages (Picamera2, OpenCV and RPi.GPIO are easiest from apt):
sudo apt update
sudo apt install -y python3 python3-pip python3-venv \
python3-picamera2 python3-opencv python3-rpi.gpio \
libcamera-apps libcamera-dev python3-libcamera libcap-devProject + virtualenv (with system site packages so Picamera2 is visible):
git clone https://github.com/milutin2002/alarmSecuritySystem.git
cd alarmSecuritySystem/PI
python3 -m venv --system-site-packages venv
source venv/bin/activate
pip3 install -r requirements.txt
pip3 install onnxruntime # not yet pinned in requirements.txtProvide the YOLOv8n model (aiDeploy.py loads yolov8n.onnx from the PI/ working dir):
cd ../Model
pip3 install ultralytics
python3 modelConvert.py # produces yolov8n.onnx
cp yolov8n.onnx ../PI/
cd ../PICreate PI/.env:
FIREBASE_CRED_PATH=your_service_account.json
FIREBASE_BUCKET=your-bucket-name.appspot.com
FIREBASE_DB=https://your-db.firebaseio.comEnable the camera (sudo raspi-config → Interface Options → Camera), adjust
IMAGE_SAVE_DIR in PI/config.py for your machine, then run:
python3 app.py # starts PIR event detection + Flask stream on :8080- Open
Android/in Android Studio. - Add
google-services.jsontoAndroid/app/. - Enable Firebase Cloud Messaging and Realtime Database for the project.
- Set the broker host in
MainViewModel.kt/MqttController.ktand the stream URL inStreamScreen.kt(currently a hard-coded LAN IP). - Build and run; the app auto-subscribes to the
alertsFCM topic.
- Alarm pipeline: arm the system from the app, move in front of the PIR — LED + buzzer fire, and a push notification with the captured image arrives.
- False-alarm filter: trigger the PIR with no person in frame — the event is captured but not uploaded (no person detected).
- Disarm: disarm from the app, trigger the PIR — no signal, no notification;
pico/alarm/statusreportsOFF. - Live stream: open the Stream screen — the MJPEG feed renders in real time.
- Camera pan: use the on-screen arrows — the servo steps left/right.
- Public MQTT broker with no authentication; the Pico connects in plaintext on
1883while Android uses TLS on8883— move to a private broker with ACLs. - Firebase Storage objects are made public (
blob.make_public()). - Several values are hard-coded: stream/broker LAN IPs (Android), Wi-Fi country
code (
WifiAgent.cpp),IMAGE_SAVE_DIR(config.py). - Only one servo (pan);
pico/servo/up/downtopics are subscribed but not handled. PI/requirements.txtis missingonnxruntime,picamera2andRPi.GPIO..gitignorecurrently contains an unresolved merge conflict.
Embedded systems (C++ / FreeRTOS) · Python hardware control · on-device AI inference · cloud integration · mobile development · full-stack IoT architecture.
MIT License
PRs and issues are welcome — fork the repo and open a pull request to improve or extend the project.






