- Rust 100%
| .cargo | ||
| src | ||
| test_images | ||
| .env.dist | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| crab-cam.png | ||
| LICENSE | ||
| README.md | ||
| TODO.md | ||
motion-camera
A CLI app intended to run on raspberry pi (or other other computer with a camera attached) and detect motion.
Features
- Multiple strategies for motion detection
- Record short video when motion is detected
- Call configured webhooks when motion is detected
- Support all common camera image formats
Building
You'll likely want to cross-compile this to aarch64 to run on the raspberry pi. To do this, you need to install the appropriate cross-compiler package for your system.
Debian:
$ sudo apt-get install gcc-aarch64-linux-gnu
Arch:
$ sudo pacman -S aarch64-linux-gnu-gcc
You'll also need to install the rust toolchain for the target architecture:
$ rustup target add aarch64-unknown-linux-gnu
Ensure the linker is specified correctly in .cargo/config
Build:
$ cargo build --release --target=aarch64-unknown-linux-gnu
Camera Backends
The default v4l backend captures YUYV frames from USB and other conventional
V4L2 cameras. Select a device with:
$ ./motion-camera --backend v4l --cam /dev/video0
Raspberry Pi CSI cameras use libcamera instead of acting like conventional V4L2 capture devices. On Raspberry Pi OS, install the camera applications and verify that the camera is detected:
$ sudo apt install rpicam-apps
$ rpicam-hello --list-cameras
Then run motion-camera with the rpicam backend:
$ mkdir -p clips
$ ./motion-camera --backend rpicam --out-dir ./clips
This backend starts the system-provided rpicam-vid command and reads its raw
YUV420 output. It captures 1280x720 video at 10 fps and downsamples the luma
plane to 320x180 for motion detection. Recorded clips retain the full 1280x720
frames. Since libcamera remains outside the Rust binary, cross-compilation does
not require Raspberry Pi libraries or a target sysroot.
The --cam option only applies to the v4l backend. The initial rpicam
backend uses camera index 0.
For higher-quality clips without software H.264 encoding, install FFmpeg and
use the rpicam-h264 backend:
$ sudo apt install rpicam-apps ffmpeg
$ mkdir -p clips
$ ./motion-camera --backend rpicam-h264 --out-dir ./clips
This backend asks rpicam-vid for a continuous 1920x1080, 30 fps, High-profile
H.264 stream at 8 Mbps. It decodes one IDR frame every 500 ms for motion
detection and writes the original encoded stream to clips without re-encoding.
Completed raw clips are remuxed to MP4 by a background FFmpeg worker, so camera
capture continues during finalization.
The H.264 backend retains approximately two seconds of encoded video before motion is detected. Clips therefore begin at an independently decodable IDR before the notification timestamp. The pre-roll buffer is also limited to 8 MiB for memory safety. Existing five-second motion expiry and 30-second maximum clip duration behavior still applies.
--interval values above 500 ms skip IDRs for detection while retaining all
encoded frames. Values below 500 ms cannot make detection faster because the
initial H.264 GOP is fixed at 15 frames.
FFmpeg is required only when --out-dir is used with rpicam-h264. If remuxing
fails, the hidden raw .h264 file is retained in the output directory for
diagnosis. Temporary raw and .part.mp4 files are removed after a successful
remux.
To verify the raw frame layout expected by motion-camera, capture ten frames on the Pi:
$ rpicam-vid --camera 0 --nopreview --frames 10 --width 1280 --height 720 \
--framerate 10 --codec yuv420 --flush --output test.yuv
$ stat -c '%s bytes' test.yuv
The expected size is 13824000 bytes (10 frames of 1,382,400 bytes each).
Testing
A suite of detection pair images can be found in ./test_images. They're sorted
into directories. All of the image pairs in ./test_images/motion should
trigger motion detection, while all of the pairs in ./test_images/no-motion
should not trigger detection. There are test cases in the detect bundle which
will run every image pair against every detection strategy.
Test for ./test_images/light-change are ignored, because some of the
strategies cannot pass them.
One way to generate test images is to set environment variable
MOTION_CAM_WRITE_DEBUG_IMAGES=true before running the program. When motion is
detected, the pair of images that triggered detection will be saved to the
current working directory. Set the similarity threshold very high to write a
detection pair for frames without motion.
Detection Handlers
There is a DetectionHandler trait for generic detection handlers. Potential
handlers could include:
- webhook: multi-purpose
- discord: send direct message
- twilio: send text message
- email: send email
- cloud uploader: upload detection clip to cloud service
Discord
Set the environment variables:
MOTION_CAM_DISCORD_BOT_TOKENCreate a bot https://discord.com/developers/applications and get the tokenMOTION_CAM_DICORD_DM_USERCurrently needs to be the user ID who will recieve DMs when motion is detected. This user must be in at least one server with the bot. The bot will not send any messages in the server.
When both of these variables are set, the DiscordHandler will be registered.
Embedded Web Server
A webserver is hosted on port 4444 with an API endpoint at PUT /detecting.
Send a boolean value (1, 0, true, false, etc) to turn motion detection on or
off. This can be used to automate turning on detection when you leave your house
for example.
In the future, this server may host a web UI to view or manage settings, and hopefully to view the live video stream eventually (via HLS or something similar).
Logging
This project uses env_logger with a default level of info. If you want to
see debug logs then set RUST_LOG=debug, or just pass the -v flag.