#!/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   ###
### /home/pi/klipper_config/webcam.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"

brokenfps_usb_devices=("046d:082b" "1908:2310" "0458:708c" "1e4e:0102" "0471:0311" "038f:6001" "046d:0804" "046d:0825" "046d:0994" "0ac8:3450")

config_dir="/home/pi/klipper_config"

echo "Starting up webcamDaemon..."
echo ""

cfg_files=()
#cfg_files+=/boot/mainsail.txt
if [[ -d ${config_dir} ]]; then
  cfg_files+=( `ls ${config_dir}/webcam*.txt` )
fi

array_camera_config=()
array_camera=()
array_camera_usb_options=()
array_camera_usb_device=()
array_camera_raspi_options=()
array_camera_http_webroot=()
array_camera_http_options=()
array_additional_brokenfps_usb_devices=()
array_camera_device=()
array_assigned_device=()

echo "--- Configuration: ----------------------------"
for cfg_file in ${cfg_files[@]}; do
  # init configuration - DO NOT EDIT, USE /home/pi/klipper_config/webcam*.txt INSTEAD!
  camera="auto"
  camera_usb_options="-r 640x480 -f 10"
  camera_raspi_options="-fps 10"
  camera_http_webroot="./www-mjpgstreamer"
  camera_http_options="-n"
  additional_brokenfps_usb_devices=()

  if [[ -e ${cfg_file} ]]; then
      source "$cfg_file"
  fi
  usb_options="$camera_usb_options"

  # if webcam device is explicitly given in /home/pi/klipper_config/webcam*.txt, save the path of the device
  # to a variable and remove its parameter from usb_options
  extracted_device=`echo $usb_options | sed 's@.*-d \(/dev/\(video[0-9]\+\|v4l/[^ ]*\)\).*@\1@'`
  if [ "$extracted_device" != "$usb_options" ]
  then
    # the camera options refer to a device, save it in a variable
    # replace video device parameter with empty string and strip extra whitespace
    usb_options=`echo $usb_options | sed 's/\-d \/dev\/\(video[0-9]\+\|v4l\/[^ ]*\)//g' | awk '$1=$1'`
  else
    extracted_device=""
  fi

  # echo configuration
  echo "cfg_file:      $cfg_file"
  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 "Explicitly USB device: $extracted_device"
  echo "-----------------------------------------------"
  echo ""

  array_camera_config+=( $cfg_file )
  array_camera+=( $camera )
  array_camera_usb_options+=("$usb_options")
  array_camera_usb_device+=("$extracted_device")
  array_camera_raspi_options+=("$camera_raspi_options")
  array_camera_http_webroot+=("$camera_http_webroot")
  array_camera_http_options+=("$camera_http_options")
  array_camera_brokenfps_usb_devices+=("${brokenfps_usb_devices[*]} ${additional_brokenfps_usb_devices[*]}")
  array_camera_device+=("")
done

# check if array contains a string
function containsString() {
  local e match="$1"
  shift
  for e; do [[ "$e" == "$match" ]] && return 0; done
  return 1
}

# 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

    # There are problems with 0x000137ab firmware on VL805 (Raspberry Pi 4}).
    # Try to autodetect offending firmware and temporarily fix the issue
    # by changing power management mode
    echo "Checking for VL805 (Raspberry Pi 4)..."
    if [[ -f /usr/bin/vl805 ]]; then
        VL805_VERSION=$(/usr/bin/vl805)
        VL805_VERSION=${VL805_VERSION#*: }
        echo " - version 0x${VL805_VERSION} detected"
        case "$VL805_VERSION" in
            00013701)
                echo "    - nothing to be done. It shouldn't cause USB problems."
                ;;
            000137ab)
                echo -e "    - \e[31mThis version is known to cause problems with USB cameras.\e[39m"
                echo -e "      You may want to downgrade to 0x0013701."
                echo -e "    - [FIXING] Trying the setpci -s 01:00.0 0xD4.B=0x41 hack to mitigate the"
                echo -e "      issue. It disables ASPM L1 on the VL805. Your board may (or may not) get"
                echo -e "      slightly hotter. For details see:"
                echo -e "      https://www.raspberrypi.org/forums/viewtopic.php?f=28&t=244421"
                setpci -s 01:00.0 0xD4.B=0x41
                ;;
            *)
                echo "   - unknown firmware version. Doing nothing."
                ;;
        esac
    else
        echo "  - It seems that you don't have VL805 (Raspberry Pi 4)."
        echo "    There should be no problems with USB (a.k.a. select() timeout)"
    fi

    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" &
        sleep 1 &
        sleep_pid=$!
        wait ${sleep_pid}
    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="$usb_options"
    device="video0"

    # check for parameter and set the device if it is given as a parameter
    input=$1
    if [[ -n $input ]]; then
        device=`basename "$input"`
    fi

    # add video device into options
    options="$options -d /dev/$device"

    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

# 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
while true; do

  # get list of usb video devices into an array
  video_devices=($(find /dev -regextype sed -regex '\/dev/video[0-9]\+' | sort -nk1.11 2> /dev/null))

  # add list of raspi camera into an array
  if [ "`vcgencmd get_camera`" = "supported=1 detected=1" ]; then
    video_devices+=( "raspi" )
  fi

  echo "Found video devices:"
  printf '%s\n' "${video_devices[@]}"

  for scan_mode in "usb" "usb-auto" "raspi" "auto"; do
    camera=$scan_mode
    if [[ "usb-auto" == "$scan_mode" ]]; then
      camera="usb"
    fi
    for ((i=0;i<${#array_camera[@]};i++));  do
      if [[ -z ${array_camera_device[${i}]} ]] && [[ $camera == ${array_camera[${i}]} ]]; then
        camera_config="${array_camera_config[${i}]}"
        usb_options="${array_camera_usb_options[${i}]}"
        camera_usb_device="${array_camera_usb_device[${i}]}"
        camera_raspi_options="${array_camera_raspi_options[${i}]}"
        camera_http_webroot="${array_camera_http_webroot[${i}]}"
        camera_http_options="${array_camera_http_options[${i}]}"
        brokenfps_usb_devices="${array_camera_brokenfps_usb_devices[${i}]}"
        if [[ ${camera_usb_device} ]] && { [[ "usb" == ${scan_mode} ]] || [[ "auto" == ${scan_mode} ]]; }; then
           # usb device is explicitly set in options
          usb_device_path=`readlink -f ${camera_usb_device}`
          if containsString "$usb_device_path" "${array_camera_device[@]}"; then
            if [[ "auto" != ${scan_mode} ]]; then
              array_camera_device[${i}]="alredy_in_use"
              echo "config file='$camera_config':Video device already in use."
              continue
            fi
          elif containsString "$usb_device_path" "${video_devices[@]}"; then
            array_camera_device[${i}]="$usb_device_path"
            # explicitly set usb device was found in video_devices array, start usb with the found device
            echo "config file='$camera_config':USB device was set in options and found in devices, start MJPG-streamer with the configured USB video device: $usb_device_path"
            startUsb "$usb_device_path"
            continue
          fi
        elif [[ -z ${camera_usb_device} ]] && { [[ "usb-auto" == ${scan_mode} ]] || [[ "auto" == ${scan_mode} ]]; }; then
          for video_device in "${video_devices[@]}"; do
            if [[ "raspi" != "$video_device" ]]; then
              if containsString "$video_device" "${array_camera_device[@]}"; then
                : #already in use
              else
                array_camera_device[${i}]="$video_device"
                # device is not set explicitly in options, start usb with first found usb camera as the device
                echo "config file='$camera_config':USB device was not set in options, start MJPG-streamer with the first found video device: ${video_device}"
                startUsb "${video_device}"
                break
              fi
            fi
          done
          if [[ -n ${array_camera_device[${i}]} ]]; then
            continue
          fi
        fi
        if [[ "raspi" == ${scan_mode} ]] || [[ "auto" == ${scan_mode} ]]; then
          video_device="raspi"
          if containsString "$video_device" "${array_camera_device[@]}"; then
            if [[ "auto" != ${scan_mode} ]]; then
              array_camera_device[${i}]="alredy_in_use"
              echo "config file='$camera_config':RasPiCam device already in use."
            fi
          elif containsString "$video_device" "${video_devices[@]}"; then
            array_camera_device[${i}]="$video_device"
            echo "config file='$camera_config':Start MJPG-streamer with video device: ${video_device}"
            startRaspi
            sleep 30 &
            sleep_pid=$!
            wait ${sleep_pid}
          fi
        fi
      fi
    done
  done
  array_assigned_device=( ${array_camera_device[*]} )
  if [[ ${#array_camera[@]} -eq ${#array_assigned_device[@]} ]]; then
    echo "Done bring up all configured video device"
    exit 0
  else
    echo "Scan again in two minutes"
    sleep 120 &
    sleep_pid=$!
    wait ${sleep_pid}
  fi
done
