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
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 !
It sounds like you missed sudo before the docker-compose command (it’s actually missing in my tutorial ).
I tried that too.
I installed a plain httpd server instead, it does the job too. Thanks for the tutorial !!