Download this tutorial as a pdf
Overview
- Set up raspberry pi (
<pi><number>) and get it connected to wifi hotspot - Connect to device using ssh
 - Update the system
 - Manage ssh connection to the device using ssh keys
 - Install editing, streaming, and networking tools (vim, libcamera, motion, tailscale)
 - Set up motion for streaming over internet
 - Get the pi display its ip address
 
1A. Get connected to wifi
- Set up the connection to wifi hotspot when burning the image and enable ssh
 - Start the pi and check on hotspot that more devices are connected
 - Find the IP address of the raspberry pi (
arp -aornmap -sn <router-ip>/24) alternatively look into the router interface to find the ip addresses this is a painpoint 
1B. Get connected to Raspberry pi using ethernet cable
- Start the Pi
 - Connect with Ethernet cable
 - Enable sharing network on WiFi
 - Run 
arp -ain powershell to see what devices are connected and get
 - ‘ssh @
 
In case you want to know how to setup multiple wifi connections for the raspberry pi, this is explained in section 15
2. Access the raspberry pi device
ssh <name>@<ip-address>- Input your password
 
3. Update the system (working)
Raspberry Pi Zero
sudo apt update && \
sudo apt full-upgrade -y && \
sudo reboot 
Raspberry Pi 4 and higher
sudo apt update && \
sudo apt full-upgrade -y && \
sudo rpi-eeprom-update -a && \
sudo reboot # for changes to take place
4. Setup access through ssh keys (working)
NOTE: Log out of the remote raspberry pi and work on your computer command line
Generate the keys on your machine
NOTE: This is needed just for the super safe connection using the public keys not password, can be skipped.
ssh-keygen -t rsa -b 4096 -f <pc>-for-<pi><number>
cp  <pc>-for-<pi><number>* .ssh/
ssh-copy-id -i <pc>-for-<pi><number>.pub <pi-ip>
Create/Add connection to this pi on given network
NOTE: This is needed just for the super safe connection using the public keys not password, can be skipped.
Open /.ssh/config
sudo vim ~/.ssh/config
Edit the file
# On S75 hotspot network
Host <pi><number>_s75
        HostName <pi-ip>
        User <user>
        IdentityFile ~/.ssh/<pc>-for-<pi><number>
Test connection
ssh <pi-ip>
5. Install tools (working)
Tools:
- noefetch to get info about the system
 - vim for editing
 - motion for streaming video
 - libcamera to make motion work with raspberry pi camera
 
Install
sudo apt install neofetch -y && \
sudo apt install vim -y && \
sudo apt install motion -y && \
sudo apt install libcamera-tools -y && \
sudo apt install libcamera-dev -y && \
sudo apt install libcamera-v4l2 -y && \
sudo sed -i 's/stream_localhost on/stream_localhost off/g' /etc/motion/motion.conf && \
echo "done"
Verify it runs
Test by running
sudo libcamerify motion -n
Access in browser at an address: <pi-ip>:8081
Shut down <Ctrl>+<C>
Start so that ssh logout will keep it running
sudo nohup libcamerify motion -n &
After pressing <Enter> you can work in the shell
NOTES:
- To close the stream get back to background process 
fgand then<Ctrl>+<C> - If it is not in the background process of this shell 
sudo pkill motionto terminate the process 
Run it as a service (works)
- Change the ownership of the directory for logs
 
sudo chown -R motion:motion /var/log/motion
- Adjust the service file
 
sudo mkdir -p /etc/systemd/system/motion.service.d
sudo tee /etc/systemd/system/motion.service.d/override.conf >/dev/null <<'EOF'
[Service]
# Keep Type/User from the main unit; no need to repeat them here.
# Replace the ExecStart from the main unit:
ExecStart=
ExecStart=/usr/bin/libcamerify /usr/bin/motion -n -c /etc/motion/motion.conf
# Optional hardening/niceties:
# Ensure files created by the service aren't world-readable
UMask=007
# Have systemd create /var/log/motion owned by the service user if supported
LogsDirectory=motion
EOF
- Reload the daemon
 
sudo systemctl daemon-reload
- Check the file really contains the changes
 
systemctl cat motion
- Start and check
 
sudo systemctl start motion
test if it is streaming in the browser http://<pi-ip>:8081
- Check if it restarts after reboot
 
sudo reboot
Should run when rebooted
Saving files when running as a service
In motion.conf, set:
target_dir /var/lib/motion/snapshots
Then on the command line:
sudo mkdir -p /var/lib/motion/snapshots
sudo chown -R motion:motion /var/lib/motion
sudo chmod 750 /var/lib/motion/snapshots
sudo usermod -aG motion <user> # so that the user can work with the files 
sudo systemctl restart motion
Create a directory in your home and bind-mount it:
mkdir -p /home/vld/shared/snapshots
Mount bind the system dir to your home path
sudo mount --bind /var/lib/motion/snapshots /home/vld/shared/snapshots
Now you’ll see the same files in both places. To make it permanent, add a line to /etc/fstab: NOT TESTED
/var/lib/motion/snapshots   /home/vld/shared/snapshots   none   bind   0   0
Add ACL entry so vld can traverse and read TESTED
sudo setfacl -m u:vld:rwx /var/lib/motion/snapshots
Retry
cd /home/vld/shared/snapshots
ls -al
6. Install tailscale for remote access (working)
Tailscale is a networking tool (like vpn) to access your devices over internet link
Install using commands and activate:
curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.noarmor.gpg | \
sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.tailscale-keyring.list | \
sudo tee /etc/apt/sources.list.d/tailscale.list
sudo apt-get update -y 
sudo apt-get install tailscale -y
sudo tailscale up
Resource
Detailed instruction how to install tailscale on Debian bookworm (on which the raspberry pi OS is based): link
7. Verify the streaming works over tailscale (working)
In browser: <tailscale-pi-name>:8081
In case it is not working:
- first reboot the raspberry pi, check that you can connect (ip address can change) and try again 
sudo reboot - check if the camera is recognized by running 
rpicam-hello --list-camerasshould list several lines (see section Cameras at the bottom of this page) 
8. Put the link online to website (working)
NOTE: works for hugo static website
Create a file webcam.md in the  content/ folder and copy inside this text with updated link and instructions:
[webcam link](http://<tilscale-pi-name>:8081)  
Activate the stream from camera over ssh:  
`nohup sudo libcamerify motion -n &`
9. Tweaks (working)
Set alias for streaming
sudo echo 'alias start-stream="sudo nohup libcamerify motion -n &" # alias to start streaming' >> ~/.bashrc
source ~/.bashrc
Now typing start-stream starts the stream in the background
Rotate the camera image
Adding rotate <number-in-degrees> into motion.conf rotates the image clockwise
# if the camera is upside down
sudo echo "rotate 180" >> /etc/motion/motion.conf
Restart the stream after the modification for it to take place
10. Motion config files (working)
The default config file for motion is in /etc/motion/motion.conf.
To run a different configuration:
- Make a copy of this file
 - Adjust it
 - Call it using
sudo libcamerify motion -n -c <path-to-config-file> 
Example config files
- Snapshots: Take a 2592x1944pxs image every 10 seconds saved in 
/var/lib/motion/snapshots/Download 
Parameters to set
width 1296height 972- to be filled in…
 
11. Installing samba for shared folder (working)
For easier transfer of files from the raspberry pi to the pc you can mount a shared folder on the pi as a network disk.
Samba project link.
Instructions for raspberry pi setup: link.
Install samba from repository
sudo apt update
sudo apt install samba samba-common-bin smbclient cifs-utils -y
Create a shared folder with the proper rights
cd ~
mkdir shared
chmod 0740 shared
Create the user who will be connecting
Think about user name and a password and write it on a piece of paper or use the same as you use to access rapsberry pi
sudo smbpasswd -a <username>
Configure samba
Open the config file
sudo vim /etc/samba/smb.conf
Add following at the end
[<share-name>] 
    path = /home/<username>/shared
    read only = no
    public = yes
    writable = yes
NOTE: The text inside the brackets is how the folder will be called when you are connecting from outside (mapping the network disk)
Check it is working
sudo systemctl status smbd
NOTE: in case you use sudo systemctl status samba gives a condition failed message which does not mean the share is not working
Conect to the folder from a Win11 machine
- Right click 
This Computer - Select the letter for a disk
 - Put 
\\<pi-ip>\<share-name>, in case it does not work, try\\<pi-ip>\<user-name>\<share-name> - Fill in the user name and a password (for samba)
 
NOTE: instead of <pi-ip> also the <pi-name> can be used but initially, the <pi-ip> is safer
12. Use USB cable for ssh connection from Win11 (not working)
in /boot/firmware/config.txt
dtoverlay=dwc2
in /boot/firmware/cmdline.txt
rootwait modules-load=dwc,g_ether
reboot and connect
check if the usb0 is up
ip a show usb0
if it is dowb bring it up
sudo ip link set usb0 up
in etc/dhcpcd.conf should set it automatically
interface usb0
static ip_address=192.168.7.2/24
nolink
sudo reboot
Is not recognized from windows side, need insatllaiton of rndis drivers, downloaded from here emm2a post link but not tested it
13. Moving files from raspberry pi (working)
First, you may want to compress files into a reasonably sized zip chunks, then use the samba folder mapped as a network drive or one of the following:
NOTE:Did not benchmark, but zipping on Raspberry Pi Zero takes ages, so I am going without it
zip -r -s 50m <archive-name>.zip /path/to/folder
To unzip the files:
unzip <archive-name>.zip
http server (working)
- Start the http server in the folder containg the files on the raspberry pi
 
sudo nohup python3 -m http.server 8000 &
- Get the files from the server
 
wget -c http://<pi-ip>:8000/<file-name>
- Kill the server from the pi
 
ps aux | grep http.server
kill <PID>
is supposed to be slower
rsync utility (not tested yet)
On your machine navigate to the folder where you want the file to land
rsync -avz --progress <user>@<pi-ip>:/path/to/<file-name> .
is supposed to be the fastest, not tested yet
14. Renaming files from the snapshot
NOTE: Fixed, is not needed for 60sec snapshots
The files from snapshots are time coded such that 18:05 is coded as 185, while 18:15 is coded as 1815.
To fix it use following rename command:
rename -n 's/(_\d{4}-\d{2}-\d{2}_)(\d{2})(\d{1}-)(\d+\.jpg)/$1${2}0${3}$4/' snapshot*.jpg
- recognized pattern enclosed in 
( .. )gets saved into a variable - regex testing site
 - needs to be done in wsl, not from GitBash
 
15. Seting up wifi connections using nmcli (working)
Better to use sudo for all of the nmcli commands
See what wifi’s are available
- Rescan the networks first
 
sudo nmcli device wifi rescan
- See what wifi connections are avalialbe
 
sudo nmcli device wifi list
- See what all connections are possible
 
sudo nmcli connection show
In the device column, check what device is used to connect to wifi (supposing you are actively connected to one)
Add a new wifi connection with a password
- Add a new connection
 
sudo nmcli connection add type wifi ifname wlan0 con-name MyWiFi ssid "MyWiFi"
ifname parameter is what you see under the device column in previous point
2. Add the password
sudo nmcli connection modify MyWiFi wifi-sec.key-mgmt wpa-psk wifi-sec.psk "mypassword123"
- Enable auto connect
 
sudo nmcli connection modify MyWiFi connection.autoconnect yes
- Set the priority for the different networks the higher connects first
 
sudo nmcli connection modify HomeWiFi connection.autoconnect-priority 10
- Verify the priorities
 
nmcli -f NAME,AUTOCONNECT-PRIORITY connection show
16. SetUp Raspberry Pi own hotspot
17. Let Raspberry Pi display its own IP address
Getting the 16x2 display work
- Using the Led display with the i2c converter needs just four wires:
 - Set up that I2C is working in raspi-config “interfacing opitons -> I2C”
 
sudo raspi-config #navigate to 
- Install drivers(?)
 
sudo apt-get install i2c-tools -y
sudo apt install wireless-tools -y
- Check it recoginzes the device
 
i2cdetect -y 1
give a table with dashes and a number, that is the number of the interface, 27 for me
- Install the virtual environments for python
 
sudo apt install python3-venv -y
- Make specific environment just for this, activate and install packages
 
python3 -m venv lcd_env
source lcd_env/bin/activate
pip install RPLCD smbus2 netifaces
- Check a demo script is running
 
from RPLCD.i2c import CharLCD
# Adjust the address if necessary (0x27 or 0x3F are common)
lcd = CharLCD('PCF8574', 0x27)
line1 = input("Enter text for line 1: ")
line2 = input("Enter text for line 2: ")
lcd.clear()
lcd.clear()
lcd.cursor_pos = (0, 0)  # row 0, column 0
lcd.write_string(line1[:16])
lcd.cursor_pos = (1, 0)  # row 1, column 0
lcd.write_string(line2[:16])
print("Text displayed. Press Ctrl+C to exit.")
try:
    while True:
        pass
except KeyboardInterrupt:
    lcd.clear()
    print("\nLCD cleared.")
python lcd_01.py
- Make a script which displays the wifi/eth network pi is connected to and displays ip address
 
from RPLCD.i2c import CharLCD
import time
import netifaces
import subprocess
# --- LCD setup ---
lcd = CharLCD('PCF8574', 0x27)  # change to 0x3F if needed
def get_ip_address(iface):
    """Return IP address for given interface, or None if not connected."""
    try:
        return netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr']
    except (KeyError, ValueError):
        return None
def get_wifi_ssid():
    """Return connected Wi-Fi SSID using 'iwgetid' command."""
    try:
        ssid = subprocess.check_output(
            ["iwgetid", "-r"], stderr=subprocess.DEVNULL
        ).decode().strip()
        return ssid if ssid else None
    except subprocess.CalledProcessError:
        return None
def get_active_connections():
    """Return dict of {display_name: ip_address} for wlan0/eth0 if active."""
    result = {}
    # Wi-Fi
    wifi_ip = get_ip_address("wlan0")
    wifi_ssid = get_wifi_ssid()
    if wifi_ip and wifi_ssid:
        result[wifi_ssid] = wifi_ip
    elif wifi_ip:
        result["wlan0"] = wifi_ip
    # Ethernet
    eth_ip = get_ip_address("eth0")
    if eth_ip:
        result["eth0"] = eth_ip
    return result
def display(interface, ip):
    """Show interface name/SSID and IP on LCD."""
    lcd.clear()
    lcd.cursor_pos = (0, 0)
    lcd.write_string(interface[:16].center(16))  # Centered text
    lcd.cursor_pos = (1, 0)
    lcd.write_string(ip[:16])
try:
    print("Starting LCD network monitor with SSID display...")
    while True:
        interfaces = get_active_connections()
        if not interfaces:
            lcd.clear()
            lcd.cursor_pos = (0, 0)
            lcd.write_string("No Network".center(16))
            lcd.cursor_pos = (1, 0)
            lcd.write_string("Connected".center(16))
            time.sleep(5)
            continue
        # Alternate display every 10 seconds
        for iface, ip in interfaces.items():
            display(iface, ip)
            time.sleep(10)
except KeyboardInterrupt:
    lcd.clear()
    print("\nLCD cleared and program stopped.")
- Make a script which clears the lcd at the end 
lcd_clear.py 
from RPLCD.i2c import CharLCD
lcd = CharLCD('PCF8574', 0x27)  # adjust address
lcd.clear()
try:
    lcd.backlight_enabled = False  # optional
except Exception:
    pass
- Set it running as a service
 
(lcd_env) vld@pi4:~/Documents/lcd $ sudo cat /etc/systemd/system/lcd_network.service
[Unit]
Description=LCD Network Display
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/home/vld/lcd_env/bin/python /home/vld/Documents/lcd/lcd_networks_ssid.py
ExecStop=/home/vld/lcd_env/bin/python /home/vld/Documents/lcd/lcd_clear.py
KillSignal=SIGINT
WorkingDirectory=/home/vld/Documents/lcd
StandardOutput=journal
StandardError=journal
Restart=always
User=vld
[Install]
WantedBy=multi-user.target
- Reload deamons see if it starts, stops
 
sudo systemctl daemon-reload
sudo systemctl enable lcd_network.service
sudo systemctl start lcd_network.service
sudo systemctl status lcd_network.service
sudo systemctl stop lcd_network.service
journalctl -u lcd_network.service -f
17. Using GPIO pins (in progress)
gpiozero library enables you to use the general purpose input output pins
Use cases
- Switch on/off a lamp, pump device connected to mains
 - Indicate by LEDs if the raspberry pi is working/online/streaming etc
 - Take measurements and do action
 
Resources
link to the projects documentation online
link to the versions of downloadable documentation
Download the current pdf docs version
System
The above works for following systems
Camera(s)
Raspberry Pi camera V1.3
Raspberry Pi Zero W
Firmware
vld@pi0D:~ $ vcgencmd version
Apr 30 2025 13:35:18
Copyright (c) 2012 Broadcom
version 5560078dcc8591a00f57b9068d13e5544aeef3aa (clean) (release) (start)
OS
vld@pi0D:~ $ neofetch --off --color_blocks off
ld@pi0D
--------
OS: Raspbian GNU/Linux 12 (bookworm) armv6l
Host: Raspberry Pi Zero W Rev 1.1
Kernel: 6.12.34+rpt-rpi-v6
Uptime: 2 hours, 33 mins
Packages: 1595 (dpkg)
Shell: bash 5.2.15
Terminal: /dev/pts/2
CPU: BCM2835 (1) @ 1.000GHz
Memory: 130MiB / 427MiB
Camera
vld@pi0D:~ $ rpicam-hello --list-cameras
Available cameras
-----------------
0 : ov5647 [2592x1944 10-bit GBRG] (/base/soc/i2c0mux/i2c@1/ov5647@36)
    Modes: 'SGBRG10_CSI2P' : 640x480 [30.00 fps - (65535, 65535)/65535x65535 crop]
                             1296x972 [30.00 fps - (65535, 65535)/65535x65535 crop]
                             1920x1080 [30.00 fps - (65535, 65535)/65535x65535 crop]
                             2592x1944 [30.00 fps - (65535, 65535)/65535x65535 crop]
Raspberry Pi 4
Firmware
vld@pi4:~ $ vcgencmd version
Aug 30 2024 19:17:39
Copyright (c) 2012 Broadcom
version 2808975b80149bbfe86844655fe45c7de66fc078 (clean) (release) (start)
OS
vld@pi4:~ $ neofetch --off --color_blocks off
vld@pi4
-------
OS: Debian GNU/Linux 12 (bookworm) aarch64
Host: Raspberry Pi 4 Model B Rev 1.4
Kernel: 6.6.51+rpt-rpi-v8
Uptime: 2 hours, 46 mins
Packages: 1610 (dpkg)
Shell: bash 5.2.15
Terminal: /dev/pts/2
CPU: (4) @ 1.800GHz
Memory: 405MiB / 7809MiB
Camera
vld@pi4:~ $ rpicam-hello --list-cameras
Available cameras
-----------------
0 : ov5647 [2592x1944 10-bit GBRG] (/base/soc/i2c0mux/i2c@1/ov5647@36)
    Modes: 'SGBRG10_CSI2P' : 640x480 [30.00 fps - (65535, 65535)/65535x65535 crop]
                             1296x972 [30.00 fps - (65535, 65535)/65535x65535 crop]
                             1920x1080 [30.00 fps - (65535, 65535)/65535x65535 crop]
                             2592x1944 [30.00 fps - (65535, 65535)/65535x65535 crop]