Raspberry Pi Construction Cam

In the beginning of 2021 we brought a really old house. This house already deflates heavily – there is no other choice than demolition. Since I think it’d be nice to see the progress as time lapse movie, I wanted to install a camera. The common term for this is construction cam. I’m aware about commercial vendors like Brinno selling those cameras, but (at least for the demoltion) i didn’t want to spend additional money. Therefor, I used parts laying around to build a simple construction camera with a Raspberry Pi 3 A+ and an USB webcam.

Acceptance criteria

There are several things I’d like to achieve / expect from my camera:

  • Take a picture using an USB attached every 30 seconds right after startup
  • Enumerate those pictures without time (there won’t be reliable times)
  • Create a protected WiFi network
  • Serve a list of all taken pictures

The first two criteria are clear – just take pictures that can later be joined to video using its ascending name. I need access to Pi via Phone/Tablet when installed outdoor so I can check if position/angle match my expectations without additional software.

Basic setup

The basic setup is simple. This tutorial expects you to connect via WiFi and run commands via SSH, it differs when using GUI (which consumes additional, probably unnecessary resources.). The steps of the basic setup are:

  • Flash Raspberry Pi OS (e. g. using Raspberry Pi Imager)
  • Create /boot/ssh file to make your Pi SSH accessible
  • Create /boot/wpa_supplicant.conf with your region and credentials:
country=DE
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
  ssid="WiFi SSID"
  scan_ssid=1
  psk="WiFi Passphrase"
  key_mgmt=WPA-PSK
}
  • Unmount device and boot your Pi
  • Optional (but recommended!): Setup a new password for the user pi
  • Update your Raspberry Pi to the latest version
sudo apt update
sudo apt upgrade -y
sudo reboot

Take Pictures

I know there are some Python scripts or even tools like motion. I didn’t want to learn how to take pictures with Python. Motion did not recognize my camera. So I just use fswebcam and a simple bash scripts to take pictures:

  • Install fswebcam
sudo apt install fswebcam -y
  • Create directory /home/pi/Pictures
  • Create take_picture.sh (or another name, if you prefer) with the following content:
#!/bin/sh
current_index=$(find /home/pi/Pictures -maxdepth 1 -type f | rev | cut -d/ -f1 | rev | sort -n | tail -n 1 | cut -d. -f1)
next_index=$(($current_index + 1))

#take picture
fswebcam --set brightness=75%  --no-banner -r 1920x1080 "/home/pi/Pictures/$next_index.jpg"

#move old picture to target directory (create if necessary)
target_dir=$((current_index/2880))
mkdir -p "/home/pi/Pictures/$target_dir"
mv "/home/pi/Pictures/$current_index.jpg" "/home/pi/Pictures/$target_dir"

This script will count all pictures in /home/pi/Pictures/ and return the amount plus one. The second command will take a picture and save it with the next number. Using this methods all pictures get named with ascending numbers without being dependent on any date/time provider. UPDATE: A friend remarked that using ls gets slower over time. I also noticed this when using the nginx listing. The script now reads the highest filename number, creates the new picture with the next number and moves the old image to a result directory. each directory will contain a maximum of 2880 pictures (one day). This should be sufficient for some days.

  • Make script executable
chmod +x take_picture.sh
  • Add the script to crontab:
* * * * * /home/pi/take_picture.sh
* * * * * ( sleep 30s ; /home/pi/take_picture.sh )

Note: Crontab can only be configured to run every minute. Sleeping 30 seconds while started in the same minute is a workaround for this.

Serve pictures

Now it’s time to serve the pictures. I’ll use nginx and let it create a listing of the taken pictures. I’m lazy and don’t want to setup, manage and configure startup of nginx, so I’ll just use Docker and Compose. Feel free to achieve this by using nginx directly 😉.

  • Install Docker
curl -sSL https://get.docker.com | sh
  • Install Compose
sudo apt install docker-compose -y
  • Create compose file /home/pi/docker-compose.yml
version: '3'
services:
  nginx:
    image: nginx
    restart: always
    ports:
      - '8080:80'
    volumes:
      - '/home/pi/Pictures:/usr/share/nginx/html'
      - '/home/pi/nginx.conf:/etc/nginx/nginx.conf'
  • Create nginx configuration /home/pi/nginx.conf
worker_processes auto;

events {
  worker_connections 1024;
}

http {
  server_tokens off;
  server {
    listen 80;
    root /usr/share/nginx/html;
    location / {
      autoindex on;
    }
  }
}
  • Start the nginx container (might take some time to pull)
sudo docker-compose up -d

Navigate to http://raspberrypi:8080 – you should see the content of /home/pi/Pictures. Thanks to the cronjob there‘ should already be several pictures.

  • Restart the Pi and check if it still takes a picture every 30 seconds. At least on the A+ you won’t be able to install additional software (at least I couldn’t make the Pi be both client and access point).

Run WiFi network

The last step is the creation of a WiFi network. With this I’ll be able to check the pictures when the camera is installed in the target location. Luckily there are several setup scripts that turn your Pi into an access point. I’ll be using RaspAp.

  • Become root (important! The script neither checks for nor switches to root which does a lot of crazy things, I had to reinstall the whole OS 😅)
sudo su
  • Run setup script
wget -q https://git.io/voEUQ -O /tmp/raspap && bash /tmp/raspap

Say yes to everything until it asks you to install VPN or ad blocking – you won’t need them, so decline the installation.

  • Reboot your Pi
  • Search for WiFi
    • SSID: raspi-webgui
    • Pass: ChangeMe
  • Connect to the WiFi
  • Navigate to the Dashboard
    • User: admin
    • Password: secret
  • Change your WiFi Password (and name, if you like)
  • Change the admin password
  • Reboot the Pi again

Verify that your Pi creates pictures after startup. It should be working although you don’t have internet access anymore. You can shutdown your Pi and install it in the expected location.

Final thoughts

I just have a cheap webcam, the quality of the pictures is not the best. For an invest of 0€ (or 35€ if you buy the Pi, the cam and a power supply) this is fine to me. I’ll probably be there every day to take some high resolution pictures. It is just for the feeling and memories 😉

PS: Joining images to movie clip is possible using ffmpeg, but model A+ does not have enough RAM for this task. You have to use another machine for this.

Updates

14.03.2023 – added sudo to docker-compose command

Sources

Setup WiFi before boot: RaspberryTips

Setup and use fswebcam: Raspberrypi-Guide

Workaround to run cron every 30 seconds: Stackoverflow

Setup Docker: Raspberry Pi Website

Directory Listing with nginx: Fuzzyblog

3 Gedanken zu „Raspberry Pi Construction Cam“

  1. Thanks a lot for the interesting project !

    However, I’m having an issue when starting docker-compose and I have idea what’s going on …


    pi@pi4:~ $ docker-compose up -d
    Traceback (most recent call last):
    File „/usr/lib/python3/dist-packages/urllib3/connectionpool.py“, line 704, in urlopen
    httplib_response = self._make_request(
    ^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/urllib3/connectionpool.py“, line 399, in _make_request
    conn.request(method, url, **httplib_request_kw)
    File „/usr/lib/python3.11/http/client.py“, line 1282, in request
    self._send_request(method, url, body, headers, encode_chunked)
    File „/usr/lib/python3.11/http/client.py“, line 1328, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
    File „/usr/lib/python3.11/http/client.py“, line 1277, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
    File „/usr/lib/python3.11/http/client.py“, line 1037, in _send_output
    self.send(msg)
    File „/usr/lib/python3.11/http/client.py“, line 975, in send
    self.connect()
    File „/usr/lib/python3/dist-packages/docker/transport/unixconn.py“, line 30, in connect
    sock.connect(self.unix_socket)
    PermissionError: [Errno 13] Permission denied

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File „/usr/lib/python3/dist-packages/requests/adapters.py“, line 489, in send
    resp = conn.urlopen(
    ^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/urllib3/connectionpool.py“, line 788, in urlopen
    retries = retries.increment(
    ^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/urllib3/util/retry.py“, line 550, in increment
    raise six.reraise(type(error), error, _stacktrace)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/six.py“, line 718, in reraise
    raise value.with_traceback(tb)
    File „/usr/lib/python3/dist-packages/urllib3/connectionpool.py“, line 704, in urlopen
    httplib_response = self._make_request(
    ^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/urllib3/connectionpool.py“, line 399, in _make_request
    conn.request(method, url, **httplib_request_kw)
    File „/usr/lib/python3.11/http/client.py“, line 1282, in request
    self._send_request(method, url, body, headers, encode_chunked)
    File „/usr/lib/python3.11/http/client.py“, line 1328, in _send_request
    self.endheaders(body, encode_chunked=encode_chunked)
    File „/usr/lib/python3.11/http/client.py“, line 1277, in endheaders
    self._send_output(message_body, encode_chunked=encode_chunked)
    File „/usr/lib/python3.11/http/client.py“, line 1037, in _send_output
    self.send(msg)
    File „/usr/lib/python3.11/http/client.py“, line 975, in send
    self.connect()
    File „/usr/lib/python3/dist-packages/docker/transport/unixconn.py“, line 30, in connect
    sock.connect(self.unix_socket)
    urllib3.exceptions.ProtocolError: (‚Connection aborted.‘, PermissionError(13, ‚Permission denied‘))

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File „/usr/lib/python3/dist-packages/docker/api/client.py“, line 214, in _retrieve_server_version
    return self.version(api_version=False)[„ApiVersion“]
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/docker/api/daemon.py“, line 181, in version
    return self._result(self._get(url), json=True)
    ^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/docker/utils/decorators.py“, line 46, in inner
    return f(self, *args, **kwargs)
    ^^^^^^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/docker/api/client.py“, line 237, in _get
    return self.get(url, **self._set_request_timeout(kwargs))
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/requests/sessions.py“, line 600, in get
    return self.request(„GET“, url, **kwargs)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/requests/sessions.py“, line 587, in request
    resp = self.send(prep, **send_kwargs)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/requests/sessions.py“, line 701, in send
    r = adapter.send(request, **kwargs)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/requests/adapters.py“, line 547, in send
    raise ConnectionError(err, request=request)
    requests.exceptions.ConnectionError: (‚Connection aborted.‘, PermissionError(13, ‚Permission denied‘))

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File „/usr/bin/docker-compose“, line 33, in
    sys.exit(load_entry_point(‚docker-compose==1.29.2‘, ‚console_scripts‘, ‚docker-compose‘)())
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/compose/cli/main.py“, line 81, in main
    command_func()
    File „/usr/lib/python3/dist-packages/compose/cli/main.py“, line 200, in perform_command
    project = project_from_options(‚.‘, options)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/compose/cli/command.py“, line 60, in project_from_options
    return get_project(
    ^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/compose/cli/command.py“, line 152, in get_project
    client = get_client(
    ^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/compose/cli/docker_client.py“, line 41, in get_client
    client = docker_client(
    ^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/compose/cli/docker_client.py“, line 170, in docker_client
    client = APIClient(use_ssh_client=not use_paramiko_ssh, **kwargs)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/docker/api/client.py“, line 197, in __init__
    self._version = self._retrieve_server_version()
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File „/usr/lib/python3/dist-packages/docker/api/client.py“, line 221, in _retrieve_server_version
    raise DockerException(
    docker.errors.DockerException: Error while fetching server API version: (‚Connection aborted.‘, PermissionError(13, ‚Permission denied‘))

    Any help or guidance ?

    Thanks a lot !

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert