==============================================================================
[Raspberry Pi] - Advanced Video Surveillance with ustreamer and motion/motion-UI
==============================================================================
.. raw:: html
Archived content: this page is no longer maintained and may contain inaccurate information.
Introduction
============
.. image:: https://raw.githubusercontent.com/lbr38/documentation/main/docs/images/raspberrypi/motion/cctv.jpg
Video surveillance has been one of the flagship projects for **Raspberry Pi** since the early days of the board. Tools like **motion** and dedicated operating systems like **motionPie** have quickly become a reference for this type of use.
However, limitations are quickly felt despite the advancements of the board. Video encoding is a CPU and RAM-intensive process, and the Raspberry Pi quickly becomes overloaded when setting up an advanced video surveillance system with multiple cameras.
Solution
========
The proposed solution here attempts to address the overload issue by separating tasks:
- The **ARM boards** (Raspberry Pi or others) are responsible for capturing and streaming the video feed generated by the connected camera.
- A **central server** receives the streams and handles the resource-intensive tasks of **motion detection** and **encoding** (motion).
The "**camera boards**" are placed in different locations inside or outside the monitored premises, taking necessary precautions to protect them from moisture if applicable.
The **server** is kept indoors, ensuring its safety, as it serves as the central point from which the user can review all events and view the live stream.
Everything is connected to the local network using **wired** connections. We exclude Wi-Fi cameras here as they can be easily **disabled** with a simple Wi-Fi jammer.
Here's an illustration:
.. image:: https://raw.githubusercontent.com/lbr38/documentation/main/docs/images/raspberrypi/motion/motion.png
Prerequisites
=============
- **1 or more Raspberry Pi** (or competing boards) for the "camera" part.
Their power can be low to moderate since their role is only to stream the feed without processing.
Personally, I use `Orange-Pi zero LTS `_ (4CPU, 512MB RAM, 1 Ethernet port, and 1 USB port).
Their **compact size** allows them to be easily installed anywhere, and their **POE** port enables powering them with a single Ethernet cable.
For the camera, I use a waterproof USB dome camera purchased from `Amazon `_ connected to the Orange-Pi's USB port.
- **1 central server**
Preferably a "home server," **as powerful as possible**. It is advisable to avoid using an ARM board that may quickly become overwhelmed during video processing. Keep in mind that the more cameras there are, the heavier the processing will be.
The server should run an OS such as Debian, CentOS...
Prepare each component:
- Install the necessary OS (e.g., Raspbian or Armbian) on each ARM board and on the central server (e.g., Debian 11).
- Update system packages and firmware if needed.
- Configure fixed IP addresses for the boards and the server.
Configuration
=============
For the rest of the article:
- I will assume that the surveillance installation is done on a **Raspberry Pi** board (as it is the most common), connected to a **USB camera or dome**, and running a Debian-based OS (Armbian/Raspbian).
- I will assume that the server is running a Debian-based OS.
Furthermore, the package manager used will be **apt**, and the package names installed will be specific to Debian systems (the names may vary if you decide to use a different OS).
Camera Configuration
--------------------
The goal here is to set up `ustreamer `_ to stream camera feeds over **http**.
The advantage of **ustreamer** compared to the well-known **mjpg-streamer** is that it offers more options, is clearer and easier to use, and, most importantly, it generates more native logs than mjpg-streamer, which greatly facilitates debugging.
Repeat this section "**Camera Configuration**" for each Raspberry Pi connected to a USB camera.
Note: All configurations are done as **root**.
Ustreamer
+++++++++
Connect to the **Raspberry Pi** via **ssh** and install some necessary packages for compilation:
.. code-block:: shell
apt install git make gcc build-essential
Start the installation of **ustreamer** by compiling it (it's easy), but first, you need to install some additional libraries:
.. code-block:: shell
# If Raspbian (Raspberry Pi OS):
apt install libevent-dev libjpeg8-dev libbsd-dev
# If using a different OS, see: https://github.com/pikvm/ustreamer#building
# Then, clone the ustreamer project:
cd /home/pi/
git clone --depth=1 https://github.com/pikvm/ustreamer
# And compile:
cd ustreamer
make
Check with **lsusb** if the connected USB camera is recognized by the system. In my case, with the USB dome camera, it displays:
.. code-block:: shell
lsusb
Bus 001 Device 008: ID 05a3:9230 ARC International Camera # USB Camera
Bus 001 Device 009: ID 0424:7800 Standard Microsystems Corp.
Bus 001 Device 007: ID 0424:2514 Standard Microsystems Corp. USB 2.0 Hub
Bus 001 Device 006: ID 0424:2514 Standard Microsystems Corp. USB 2.0 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Creating the stream start and stop scripts, the user **pi** will execute these scripts:
.. code-block:: shell
mkdir -p /home/pi/scripts/stream
Stream start script:
.. code-block:: shell
vim /home/pi/scripts/stream/start-stream.sh
Insert the following content:
.. code-block:: shell
#!/bin/bash
DATE=$(date +%Y-%m-%d)
TIME=$(date +%Hh%M)
RESOLUTION="1920x1080"
FRAMERATE="25"
USTREAMER="/home/pi/ustreamer/ustreamer"
LOG="/home/pi/scripts/stream/ustreamer.log"
function help()
{
echo "Usage: $0 [options]"
echo "Options:"
echo " --1080p"
echo " --720p"
echo " --low"
echo " --fps=FRAMERATE"
echo " --help"
}
while [ $# -ge 1 ];do
case "$1" in
--1080p)
RESOLUTION="1920x1080"
;;
--720p)
RESOLUTION="1280x720"
;;
--low)
RESOLUTION="640x480"
;;
--fps)
FRAMERATE="$2"
shift
;;
--help)
help
exit
;;
*)
esac
shift
done
# Cleaning log file
echo -n> "$LOG"
exec &> >(tee -a "$LOG")
echo "$DATE - $TIME - Starting stream"
"$USTREAMER" --device=/dev/video0 --slowdown --workers 2 -e 30 -K 0 -r "$RESOLUTION" -m MJPEG --host 0.0.0.0 --port 8888 --device-timeout 2 --device-error-delay 1 2>&1 &
exit
Stream stop script:
.. code-block:: shell
vim /home/pi/scripts/stream/stop-stream.sh
Insert the following content:
.. code-block:: shell
#!/bin/bash
# Search for the process ID of ustreamer
PID="$(/bin/ps -aux | /bin/grep 'ustreamer' | egrep -v 'grep|ustreamer.log' | /usr/bin/awk '{print $2}')"
if [ -z "$PID" ];then
echo "No active process found"
exit
fi
echo "Stopping ustreamer... "
kill "$PID" > /dev/null 2>&1
sleep 1
# Check if the process is still running
if /bin/ps -aux | /bin/grep 'ustreamer' | egrep -v 'grep|ustreamer.log';then
echo "Process is still running, killing it"
kill -9 "$PID"
exit
fi
echo "OK"
exit
Adjust the permissions for what was just created:
.. code-block:: shell
chmod 700 /home/pi/scripts/stream/*.sh
chown -R pi:pi /home/pi/scripts
Temporarily log in as **pi** and start the stream to test. It is possible to specify a resolution and framerate as parameters for the start stream script. By default, the stream is launched with **1920x1080** resolution and **25 fps**:
.. code-block:: shell
su pi
/home/pi/scripts/stream/start-stream.sh &
# Example to start the stream in 720p and 30 fps:
/home/pi/scripts/stream/start-stream.sh --720p --fps 30 &
It should display some logs on the screen.
Open http://CAMERA_IP_ADDRESS:8888 in a browser, the ustreamer homepage should be accessible, and the **stream** can be viewed by clicking on **/stream**.
Still as **pi**, create a cron task that will automatically start the stream after rebooting the Raspberry Pi:
.. code-block:: shell
crontab -e
@reboot /home/pi/scripts/start-camera.sh &
Server Configuration
--------------------
The goal here is to set up **motion-UI** (a web interface for **motion**) to analyze the camera streams in the house and detect motion.
Notes:
- The system used here is Debian 11.
- All configurations are performed as **root**.
motion-UI
+++++++++
Overview
~~~~~~~~
**motion-UI** is a web interface developed to manage the operation and configuration of **motion** more easily.
It is an open-source project available on GitHub: https://github.com/lbr38/motion-UI
The interface presents itself as very simplistic and **responsive**, allowing for mobile usage (Android application available here: https://github.com/lbr38/motion-UI/releases/tag/android-1.0).
It also allows the setup of **email alerts** in case of motion detection, and it can automatically enable or disable video surveillance based on a specified time range or the presence of "trusted" devices on the local network (e.g., smartphones).
.. raw:: html
The interface is divided into several tabs:
- An tab dedicated to cameras and **live stream**. The cameras are then arranged in grids on the screen (at least on a PC screen), somewhat like the surveillance screens of a facility, for example.
- An tab for starting and stopping the service **motion** and associated services (**automatic startup**, **alerts** in case of detection).
- An tab listing the **events** that have occurred and been detected by motion, with the ability to view the images or videos captured directly from the web page.
- An tab with a few graphs summarizing the recent activity of the motion service and the events that have occurred.
Installing docker
~~~~~~~~~~~~~~~~~
Start by installing the package repository for **docker**:
.. code-block:: shell
apt install ca-certificates curl gnupg -y
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Then install **docker** :
.. code-block:: shell
apt update -y
apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin -y
Installation of motion-UI
~~~~~~~~~~~~~~~~~~~~~~~~~
The installation should be done with a regular user (non-root).
Pull the latest available image and adapt the ``FQDN`` value to your domain name:
.. code-block:: shell
docker run -d --restart always --name motionui \
-e FQDN=motionui.example.com \
-p 8080:8080 \
-v /etc/localtime:/etc/localtime:ro \
-v /var/lib/docker/volumes/motionui-data:/var/lib/motionui \
-v /var/lib/docker/volumes/motionui-captures:/var/lib/motion \
lbr38/motionui:latest
Two persistent volumes are created on the host system:
- **motionui_data** ``/var/lib/docker/volumes/motionui-data/``: contains the motion-UI database.
- **motionui-captures** ``/var/lib/docker/volumes/motionui-captures/``: stores the images and videos captured by motion (keep this data!).
Once the installation is complete, motion-UI is accessible directly (without SSL certificate for now) at http://:8080
Use the default credentials to authenticate:
- Login: **admin**
- Password: **motionui**
After logging in, you can change your password from the user profile (top right corner).
Proceed with setting up a reverse-proxy to access motion-UI with a dedicated domain name and SSL certificate.
Reverse-proxy nginx
~~~~~~~~~~~~~~~~~~~
Install nginx:
.. code-block:: shell
apt install nginx -y
Remove the default vhost:
.. code-block:: shell
rm /etc/nginx/sites-enabled/default
Then create a new vhost dedicated to **motion-UI**:
.. code-block:: shell
vim /etc/nginx/sites-available/motionui.conf
Insert the following content, adapting certain values:
- The parameter should be set to the server's IP address.
- The parameters should be set to the dedicated domain name for motion-UI.
- The paths to the SSL certificate and associated private key ( and ) should be provided accordingly.
.. code-block:: shell
upstream motionui_docker {
server 127.0.0.1:8080;
}
# Disable some logging
map $request_uri $loggable {
/ajax/controller.php 0;
default 1;
}
server {
listen :80;
server_name ;
access_log /var/log/nginx/_access.log combined if=$loggable;
error_log /var/log/nginx/_error.log;
return 301 https://$server_name$request_uri;
}
server {
listen :443 ssl;
server_name ;
# Path to SSL certificate/key files
ssl_certificate ;
ssl_certificate_key ;
# Path to log files
access_log /var/log/nginx/_ssl_access.log combined if=$loggable;
error_log /var/log/nginx/_ssl_error.log;
# Security headers
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Download-Options "noopen" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "none" always;
add_header X-XSS-Protection "1; mode=block" always;
# Remove X-Powered-By, which is an information leak
fastcgi_hide_header X-Powered-By;
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://motionui_docker;
}
}
Create a symbolic link to enable the vhost:
.. code-block:: shell
ln -s /etc/nginx/sites-available/motionui.conf /etc/nginx/sites-enabled/motionui.conf
Restart nginx to apply the changes:
.. code-block:: shell
nginx -t && systemctl restart nginx
motion-UI is now accessible at https://
Adding a Camera
~~~~~~~~~~~~~~~
Use the **+** button to add a camera.
- Specify if the camera provides a **video stream** or just a **static image** that requires reloading (if yes, specify the refresh interval in seconds).
- Provide a name and the URL to the camera's **video/image stream**.
- Choose to enable motion detection on this camera. Note that if the selected stream is a static image, a second URL pointing to a video stream needs to be specified because motion is unable to perform motion detection on a stream of static images (it is not capable of automatically reloading the image).
- Specify a username/password if the stream is protected.
.. raw:: html
Once the camera is added, motion-UI automatically creates the **motion configuration** for this camera. Note that the created motion configuration is relatively minimalistic but sufficient to function in all cases. It is possible to modify this configuration in advanced mode and add custom parameters if needed (see **Camera Configuration** section).
Camera Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~
If there is a need to modify the configuration of a camera, simply click on the **Configure** button.
.. raw:: html
From here, it is possible to modify the general settings of the camera (e.g., **name**, **URL**, etc.) and change the **rotation** of the image.
It is also possible to modify the **motion configuration** of the camera (motion detection).
Please note that it is recommended to **avoid modifying motion parameters in advanced mode**, or at least not without knowing precisely what you are doing.
For example, **it is better to avoid** modifying the following parameters:
- The name and URL parameters (**camera_name**, **netcam_url**, **netcam_userpass**, and **rotate**) have values derived from the general camera settings. Therefore, it is necessary to modify them directly from the **Global settings** fields.
- Parameters related to codecs (**picture_type** and **movie_codec**) should not be modified, or else you may no longer be able to view the captures directly from motion-UI.
- Event parameters (**on_event_start**, **on_event_end**, **on_movie_end**, and **on_picture_save**) should not be modified, as it may result in the inability to record motion detection events and receive alerts.
Testing Event Recording
~~~~~~~~~~~~~~~~~~~~~~~
To do this from the **motion-UI** interface: manually start motion by clicking the **Start capture** button.
.. raw:: html
Then, **make a movement** in front of a camera to trigger an event.
If everything goes well, a new ongoing event should appear after a few seconds in the **motion-UI** interface.
Automatic Start and Stop of Motion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use the **Enable and configure autostart** button to activate and configure automatic startup.
.. raw:: html
Two types of automatic startup and shutdown of motion can be configured:
- Based on the specified time ranges for each day. The **motion** service will be active **between** the specified time range.
- Based on the presence of one or more connected IP devices on the local network. If none of the configured devices are present on the local network, the motion service will start, assuming that no one is present at home. Motion-UI regularly sends a **ping** to determine if the device is present on the network, so make sure to configure static IP leases from the router for each device at home (smartphones).
.. raw:: html
Configure Alerts
~~~~~~~~~~~~~~~~
Use the **Enable and configure alerts** button to enable and configure the alerts.
.. raw:: html
Configuring alerts requires two points of configuration:
- An **SPF record** for the domain name dedicated to motion-UI.
- The event recording must be working (see '**Testing Event Recording**').
Alert Time Slots Configuration
******************************
- Specify the **time slots** during which you want to **receive alerts** if motion is detected. To enable alerts for an **entire day**, enter 00:00 for both the start and end slots.
- Enter the recipient email address(es) that will receive the alert emails. Multiple email addresses can be specified by separating them with commas.
.. raw:: html
Testing Alerts
**************
Once the previously mentioned points have been properly configured and the **motionui** service is running, you can test the sending of alerts.
To do this from the **motion-UI** interface:
- Temporarily disable motion's autostart if enabled, to prevent it from stopping motion just in case.
- Manually start motion (**Start capture**).
Then, **make a movement** in front of a camera to trigger an alert.
Security
========
Now that the video surveillance system is operational, it is time to **secure** the entire setup.
I cannot go into detail about all the security configurations to implement, but here are some basic ideas:
- The camera streams should **only be accessible by the server**.
In other words, the access URLs to ustreamer http://CAMERA_IP_ADDRESS:8888 should only be accessible by the server.
To achieve this, establish **firewall rules** (such as iptables) on the Raspberry Pis to allow only the server to access them via HTTP.
- The SSH configuration of the cameras should be **strengthened** (using key authentication, disallowing root login, etc.).
Ideally, implement firewall rules that allow only the server and possibly another local network IP (as a backup) to connect via SSH.
- The server is the central entry point and should be made **as secure as possible**.
Start by implementing **robust firewall rules** to allow only certain IPs to connect via SSH from the local network.
Implement a **strengthened SSH configuration** (using key authentication, disallowing root login, etc.).
If you want to access it from outside (e.g., to access **motion-UI**), the best solution is to set up a **VPN** that allows access to the home network from outside (the Freebox router supports this). Another solution would be to set up port forwarding on the router, but in this case, intrusion attempts will be immediate, and the forwarded ports will be constantly scanned by internet bots.
.. raw:: html