OctoPrint is a great tool to control your 3D printer but offers by default only one camera (which can be the native raspberry one or a usb one). But one camera is not enough to be able to monitor the hotend and to take a nice timelapse of your item while printing. So I decided to add a second camera to the raspberry. There is an excellent plugin “MultiCam” that does most of the job, but there is still some configuration to do.

First you need another camera 🙂 As the camera slot is already used by the main camera we will need to use a USB camera. There is very cheap ones like this one I bought for about 2€. It is a very low quality and the image is not very good, but it is good enough for what I want to do (note that you may need to adjust focus). Depending on your raspberry’s number of USB ports and if you are connected by ethernet or with a Wifi USB key you may also need a USB Hub. You will also find cheap ones for about 1€. Note that the USB power from the raspberry is low, and you may experience some problems. You need to be able to connect through SSH to your raspberry to have access to the command line and to be able to modify some configuration files.

Once connected you must first check your USB camera is working properly. We will use mjpg_streamer for that :

cd ~/mjpg-streamer/
./mjpg_streamer -o "output_http.so -w ./www -n -p 8090" -i "input_uvc.so"

You may then check on http://octopi.local:8090 that you are viewing an image properly.

Please note that :

  • You may need to adjust parameters of input regarding your camera (like size, framerate,…)
  • The position of the quotes is not an error : the plugin and its parameters should be in the same parameter, so you have to quote it. If you must add a framerate paramater is will be : -i “input_uvc.so -f 10”
  • It is mandatory to use another port than the default 8080 because this one will be used with the raspberry camera

If the camera is working well we will automate the launch of the camera by modifying the /root/bin/webcamd script. As it is in root folder you must first sudo to root (with ‘sudo su’). Here is the resulting script :

#!/bin/bash

########################################################################
### DO NOT EDIT THIS FILE TO CHANGE THE CONFIG!!!                    ###
### ---------------------------------------------------------------- ###
### There is no need to edit this file for changing resolution,      ###
### frame rates or any other mjpg-streamer parameters. Please edit   ###
### /boot/octopi.txt instead - that's what it's there for! You can   ###
### even do this with your Pi powered down by directly accessing the ###
### file when using the SD card as thumb drive in your regular       ###
### computer.                                                        ###
########################################################################

MJPGSTREAMER_HOME=/home/pi/mjpg-streamer
MJPGSTREAMER_INPUT_USB="input_uvc.so"
MJPGSTREAMER_INPUT_RASPICAM="input_raspicam.so"

# init configuration - DO NOT EDIT, USE /boot/octopi.txt INSTEAD!
# BEGIN - MODIFICATION
camera="both"
#camera_usb_options="-r 640x480 -f 10"
camera_usb_options=""
camera_raspi_options="-fps 10"
camera_http_webroot="./www-octopi"
camera_http_options="-n"

camera_http_options_both_base="$camera_http_options"
camera_http_options_both_usb="-p 8090"
additional_brokenfps_usb_devices=()

#if [ -e "/boot/octopi.txt" ]; then
#    source "/boot/octopi.txt"
#fi

# END - MODIFICATION

brokenfps_usb_devices=("046d:082b" "${additional_brokenfps_usb_devices[@]}")

# cleans up when the script receives a SIGINT or SIGTERM
function cleanup() {
    # make sure that all child processed die when we die
    local pids=$(jobs -pr)
    [ -n "$pids" ] && kill $pids
    exit 0
}

# says goodbye when the script shuts down
function goodbye() {
    # say goodbye
    echo ""
    echo "Goodbye..."
    echo ""
}

# runs MJPG Streamer, using the provided input plugin + configuration
function runMjpgStreamer {
    input=$1
    pushd $MJPGSTREAMER_HOME > /dev/null 2>&1
        echo Running ./mjpg_streamer -o "output_http.so -w $camera_http_webroot $camera_http_options" -i "$input"
        LD_LIBRARY_PATH=. ./mjpg_streamer -o "output_http.so -w $camera_http_webroot $camera_http_options" -i "$input" &
# BEGIN - MODIFICATION
#        wait
# END - MODIFICATION
    popd > /dev/null 2>&1
}

# starts up the RasPiCam
function startRaspi {
    logger -s "Starting Raspberry Pi camera"
    runMjpgStreamer "$MJPGSTREAMER_INPUT_RASPICAM $camera_raspi_options"
}

# starts up the USB webcam
function startUsb {
    options="$camera_usb_options"
    device="video0"
    
    extracted_device=`echo $options | sed 's@.*-d /dev/\(video[0-9]+\).*@\1@'`
    if [ "$extracted_device" != "$options" ]
    then
        # the camera options refer to another device, use that for determining product
        device=$extracted_device
    fi

    uevent_file="/sys/class/video4linux/$device/device/uevent"
    if [ -e $uevent_file ]; then
        # let's see what kind of webcam we have here, fetch vid and pid...
        product=`cat $uevent_file | grep PRODUCT | cut -d"=" -f2`
        vid=`echo $product | cut -d"/" -f1`
        pid=`echo $product | cut -d"/" -f2`
        vidpid=`printf "%04x:%04x" "0x$vid" "0x$pid"` 

        # ... then look if it is in our list of known broken-fps-devices and if so remove
        # the -f parameter from the options (if it's in there, else that's just a no-op)
        for identifier in ${brokenfps_usb_devices[@]};
        do
            if [ "$vidpid" = "$identifier" ]; then
                echo
                echo "Camera model $vidpid is known to not work with -f parameter, stripping it out"
                echo
                options=`echo $options | sed -e "s/\(\s\+\|^\)-f\s\+[0-9]\+//g"`
            fi
        done
    fi

    logger -s "Starting USB webcam"
    runMjpgStreamer "$MJPGSTREAMER_INPUT_USB $options"
}

# make sure our cleanup function gets called when we receive SIGINT, SIGTERM
trap "cleanup" SIGINT SIGTERM
# say goodbye when we EXIT
trap "goodbye" EXIT

# echo configuration
echo "Starting up webcamDaemon..."
echo ""
echo "--- Configuration: ----------------------------"
echo "camera:        $camera"
echo "usb options:   $camera_usb_options"
echo "raspi options: $camera_raspi_options"
echo "http options:  -w $camera_http_webroot $camera_http_options"
echo "-----------------------------------------------"
echo ""

# we need this to prevent the later calls to vcgencmd from blocking
# I have no idea why, but that's how it is...
vcgencmd version > /dev/null 2>&1

# keep mjpg streamer running if some camera is attached
# BEGIN - MODIFICATION
while true; do
    if [ -e "/dev/video0" ] && { [ "$camera" = "auto" ] || [ "$camera" = "usb" ] || [ "$camera" = "both" ]; }; then
	if [ "`vcgencmd get_camera`" = "supported=1 detected=1" ] && [ "$camera" = "both" ]; then
                camera_http_options="$camera_http_options_base"
 		startRaspi
		sleep 10 &
		camera_http_options="$camera_http_options_both_base $camera_http_options_both_usb"
	fi
	startUsb
	sleep 10 &
# END - MODIFICATION
        wait
    elif [ "`vcgencmd get_camera`" = "supported=1 detected=1" ] && { [ "$camera" = "auto" ] || [ "$camera" = "both" ] || [ "$camera" = "raspi" ] ; }; then
        startRaspi
        sleep 30 &
        wait
    else
        echo "No camera detected, trying again in two minutes"
        sleep 120 &
        wait
    fi
done

I have modified three sections delimited by the comments “# BEGIN/END MODIFICATION” :

  • In the parameters sections I have added parameters to be able to run both cameras and disabled the use of /boot/octopi.txt to have to modify only one file
  • I commented the wait just after the mjpg_streamer command to be able to run another one
  • I modified the script to support both cameras at the same time. Note that the script should work if you disconnect one of the cameras (or modify back the camera parameter to another value than ‘both’)

Then you will need to modify the /etc/haproxy/haproxy.conf to add an URL that may be used in octoprint.

global
        maxconn 4096
        user haproxy
        group haproxy
        log 127.0.0.1 local1 debug

defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        retries 3
        option redispatch
        option http-server-close
        option forwardfor
        maxconn 2000
        timeout connect 5s
        timeout client  15min
        timeout server  15min

frontend public
        bind *:80
        bind 0.0.0.0:443 ssl crt /etc/ssl/snakeoil.pem
        option forwardfor except 127.0.0.1
        use_backend webcam if { path_beg /webcam/ }
	use_backend usbcam if { path_beg /usbcam/ }
        default_backend octoprint

backend octoprint
        acl needs_scheme req.hdr_cnt(X-Scheme) eq 0

        reqrep ^([^\ :]*)\ /(.*) \1\ /\2
        reqadd X-Scheme:\ https if needs_scheme { ssl_fc }
        reqadd X-Scheme:\ http if needs_scheme !{ ssl_fc }
        option forwardfor
        server octoprint1 127.0.0.1:5000
        errorfile 503 /etc/haproxy/errors/503-no-octoprint.http

backend webcam
        reqrep ^([^\ :]*)\ /webcam/(.*)     \1\ /\2
        server webcam1  127.0.0.1:8080
        errorfile 503 /etc/haproxy/errors/503-no-webcam.http

backend usbcam
        reqrep ^([^\ :]*)\ /usbcam/(.*)     \1\ /\2
        server webcam1  127.0.0.1:8090
        errorfile 503 /etc/haproxy/errors/503-no-webcam.http



It adds /ubscam/ to access the new port :8090 we created above.

It is now time to go to OctoPrint to finalize the configuration. To ensure the new files are taken in account, you must restart the corresponding services. If you are not sure, just restart the raspberry. In OctoPrint, add the “MultiCam” plugin. Then go to its configuration and add another camera with URL “/usbcam/?action=stream”. Save, and that’s it !