Files
picUploadApi/demo/capture-upload.sh
sebseb7 bd6b20e6ed u
2025-12-21 22:48:26 +01:00

456 lines
15 KiB
Bash
Executable File

#!/bin/bash
# capture-upload.sh - Capture from USB webcam and upload to PicUpper
# For Raspberry Pi 3 with Logitech USB webcam
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${SCRIPT_DIR}/picupper.conf"
# ==============================================================================
# Load Configuration
# ==============================================================================
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "ERROR: Configuration file not found: $CONFIG_FILE"
echo "Please copy picupper.conf.example to picupper.conf and configure it."
exit 1
fi
# shellcheck source=/dev/null
source "$CONFIG_FILE"
# Validate required settings
if [[ -z "${API_KEY:-}" || "$API_KEY" == "your-api-key-here" ]]; then
echo "ERROR: API_KEY not configured in $CONFIG_FILE"
exit 1
fi
# Set defaults for optional settings
API_URL="${API_URL:-https://dev.seedheads.de/picUploadApi/upload}"
CAMERA_ID="${CAMERA_ID:-rpi-webcam}"
VIDEO_DEVICE="${VIDEO_DEVICE:-/dev/video0}"
RESOLUTION="${RESOLUTION:-1920x1080}"
CAPTURE_METHOD="${CAPTURE_METHOD:-fswebcam}"
SKIP_FRAMES="${SKIP_FRAMES:-5}"
TEMP_DIR="${TEMP_DIR:-/tmp}"
LOG_FILE="${LOG_FILE:-${SCRIPT_DIR}/picupper.log}"
TIMEOUT="${TIMEOUT:-30}"
MAX_RETRIES="${MAX_RETRIES:-3}"
RETRY_DELAY="${RETRY_DELAY:-5}"
# ==============================================================================
# Helper Functions
# ==============================================================================
log() {
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local msg="[$timestamp] $*"
echo "$msg"
echo "$msg" >> "$LOG_FILE" 2>/dev/null || true
}
log_error() {
log "ERROR: $*" >&2
}
cleanup() {
rm -f "${TEMP_FILE:-}" 2>/dev/null || true
}
trap cleanup EXIT
# ==============================================================================
# Test Mode
# ==============================================================================
if [[ "${1:-}" == "--test" ]]; then
echo "=== PicUpper Test Mode ==="
echo "Config file: $CONFIG_FILE"
echo "API URL: $API_URL"
echo "Camera ID: $CAMERA_ID"
echo "Video device: $VIDEO_DEVICE"
echo "Resolution: $RESOLUTION"
echo "Capture Method: $CAPTURE_METHOD"
echo ""
# Check video device (only if using fswebcam or if device is specified for rpicam)
if [[ "$CAPTURE_METHOD" == "fswebcam" ]]; then
if [[ ! -e "$VIDEO_DEVICE" ]]; then
echo "❌ Video device not found: $VIDEO_DEVICE"
echo " Available devices:"
ls -la /dev/video* 2>/dev/null || echo " No video devices found"
exit 1
fi
echo "✓ Video device exists: $VIDEO_DEVICE"
# Check fswebcam
if ! command -v fswebcam &>/dev/null; then
echo "❌ fswebcam not installed. Run: sudo apt install fswebcam"
exit 1
fi
echo "✓ fswebcam installed"
elif [[ "$CAPTURE_METHOD" == "rpicam-still" ]] || [[ "$CAPTURE_METHOD" == "libcamera-still" ]]; then
# Check rpicam-still/libcamera-still
CMD_NAME="rpicam-still"
if ! command -v rpicam-still &>/dev/null; then
if command -v libcamera-still &>/dev/null; then
CMD_NAME="libcamera-still"
else
echo "❌ rpicam-still (or libcamera-still) not installed."
exit 1
fi
fi
echo "$CMD_NAME installed"
else
echo "❌ Unknown CAPTURE_METHOD: $CAPTURE_METHOD"
exit 1
fi
# Try capture
# Get System Info for Overlay
SYSTEM_HOSTNAME=$(hostname)
SYSTEM_IP=$(hostname -I 2>/dev/null | awk '{print $1}') || SYSTEM_IP="unknown"
echo "Capturing test image to: $TEST_FILE"
if [[ "$CAPTURE_METHOD" == "rpicam-still" ]] || [[ "$CAPTURE_METHOD" == "libcamera-still" ]]; then
# Parse resolution
WIDTH=$(echo "$RESOLUTION" | cut -d'x' -f1)
HEIGHT=$(echo "$RESOLUTION" | cut -d'x' -f2)
CMD="rpicam-still"
command -v libcamera-still &>/dev/null && CMD="libcamera-still"
if $CMD -o "$TEST_FILE" --width "$WIDTH" --height "$HEIGHT" --nopreview --timeout 2000; then
FILE_SIZE=$(stat -f%z "$TEST_FILE" 2>/dev/null || stat -c%s "$TEST_FILE" 2>/dev/null)
echo "✓ Capture successful: $TEST_FILE (${FILE_SIZE} bytes)"
else
echo "❌ Capture failed."
exit 1
fi
else
# fswebcam default
if fswebcam -d "$VIDEO_DEVICE" -r "$RESOLUTION" -S "$SKIP_FRAMES" \
--title "$CAMERA_ID" \
--subtitle "$SYSTEM_IP" \
--banner-colour '#AA000000' \
--line-colour '#FF000000' \
--text-colour '#FFFFFF' \
--font "sans:24" \
--timestamp "%Y-%m-%d %H:%M:%S" \
"$TEST_FILE" 2>/dev/null; then
FILE_SIZE=$(stat -f%z "$TEST_FILE" 2>/dev/null || stat -c%s "$TEST_FILE" 2>/dev/null)
echo "✓ Capture successful: $TEST_FILE (${FILE_SIZE} bytes)"
echo ""
echo "To view the image: gpicview $TEST_FILE"
else
echo "❌ Capture failed. Check webcam connection and permissions."
exit 1
fi
fi
exit 0
fi
# ==============================================================================
# Main Capture & Upload
# ==============================================================================
TEMP_FILE="${TEMP_DIR}/picupper_${CAMERA_ID}_$(date +%s).jpg"
# Check video device (only for fswebcam)
if [[ "$CAPTURE_METHOD" == "fswebcam" ]]; then
if [[ ! -e "$VIDEO_DEVICE" ]]; then
log_error "Video device not found: $VIDEO_DEVICE"
exit 1
fi
fi
# Discover available v4l2 controls and report them to server
discover_and_report_controls() {
if ! command -v v4l2-ctl &>/dev/null; then
log "v4l2-ctl not installed, skipping control discovery"
return
fi
local AVAILABLE_URL="${API_URL%/upload}/settings/$CAMERA_ID/available"
log "Discovering available camera controls..."
# Parse v4l2-ctl -L output into JSON
# Example lines:
# brightness 0x00980900 (int) : min=-64 max=64 step=1 default=0 value=0
# exposure_auto 0x009a0901 (menu) : min=0 max=3 default=3 value=3
# 1: Manual Mode
# 3: Aperture Priority Mode
local controls_json="["
local current_values_json="{"
local first_control=true
local first_value=true
local current_ctrl=""
local ctrl_json=""
local menu_options=""
local in_menu=false
while IFS= read -r line || [[ -n "$line" ]]; do
# Check if this is a control line (starts with control name)
if [[ "$line" =~ ^[[:space:]]*([a-z_]+)[[:space:]]+0x[0-9a-f]+[[:space:]]+\(([a-z]+)\)[[:space:]]*:[[:space:]]*(.*) ]]; then
# Save previous control if exists
if [[ -n "$current_ctrl" ]]; then
if [[ "$in_menu" == "true" && -n "$menu_options" ]]; then
ctrl_json="${ctrl_json%, }, \"options\": {${menu_options%,}}}"
else
ctrl_json="${ctrl_json%,}}"
fi
if [[ "$first_control" == "true" ]]; then
first_control=false
else
controls_json+=","
fi
controls_json+="$ctrl_json"
fi
current_ctrl="${BASH_REMATCH[1]}"
local ctrl_type="${BASH_REMATCH[2]}"
local params="${BASH_REMATCH[3]}"
ctrl_json="{\"name\": \"$current_ctrl\", \"type\": \"$ctrl_type\","
menu_options=""
in_menu=false
# Parse parameters
if [[ "$params" =~ min=(-?[0-9]+) ]]; then
ctrl_json+=" \"min\": ${BASH_REMATCH[1]},"
fi
if [[ "$params" =~ max=(-?[0-9]+) ]]; then
ctrl_json+=" \"max\": ${BASH_REMATCH[1]},"
fi
if [[ "$params" =~ step=([0-9]+) ]]; then
ctrl_json+=" \"step\": ${BASH_REMATCH[1]},"
fi
if [[ "$params" =~ default=(-?[0-9]+) ]]; then
ctrl_json+=" \"default\": ${BASH_REMATCH[1]},"
fi
if [[ "$params" =~ value=(-?[0-9]+) ]]; then
local current_value="${BASH_REMATCH[1]}"
if [[ "$first_value" == "true" ]]; then
first_value=false
else
current_values_json+=","
fi
current_values_json+="\"$current_ctrl\": $current_value"
fi
if [[ "$ctrl_type" == "menu" ]]; then
in_menu=true
fi
# Check if this is a menu option line (indented with number: description)
elif [[ "$in_menu" == "true" && "$line" =~ ^[[:space:]]+([0-9]+):[[:space:]]+(.+)$ ]]; then
local opt_val="${BASH_REMATCH[1]}"
local opt_name="${BASH_REMATCH[2]}"
menu_options+="\"$opt_val\": \"$opt_name\","
fi
done < <(v4l2-ctl -d "$VIDEO_DEVICE" -L 2>/dev/null)
# Save last control
if [[ -n "$current_ctrl" ]]; then
if [[ "$in_menu" == "true" && -n "$menu_options" ]]; then
ctrl_json="${ctrl_json%, }, \"options\": {${menu_options%,}}}"
else
ctrl_json="${ctrl_json%,}}"
fi
if [[ "$first_control" != "true" ]]; then
controls_json+=","
fi
controls_json+="$ctrl_json"
fi
controls_json+="]"
current_values_json+="}"
# POST to server
local payload="{\"controls\": $controls_json, \"currentValues\": $current_values_json}"
local response
response=$(curl -s --max-time 10 \
-X POST \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "$payload" \
"$AVAILABLE_URL" 2>/dev/null) || true
if echo "$response" | grep -q '"success"'; then
local count
count=$(echo "$response" | grep -o '"controlsRegistered":[0-9]*' | cut -d: -f2)
log "Reported $count available controls to server"
else
log "Failed to report available controls: $response"
fi
}
# Fetch and apply camera settings from server
apply_camera_settings() {
if ! command -v v4l2-ctl &>/dev/null; then
log "v4l2-ctl not installed, skipping camera settings"
return
fi
local SETTINGS_URL="${API_URL%/upload}/settings/$CAMERA_ID"
log "Fetching camera settings from server..."
local SETTINGS_RESPONSE
SETTINGS_RESPONSE=$(curl -s --max-time 10 \
-H "X-API-Key: $API_KEY" \
"$SETTINGS_URL" 2>/dev/null) || true
if [[ -z "$SETTINGS_RESPONSE" ]]; then
log "Could not fetch settings, discovering and reporting controls..."
discover_and_report_controls
return
fi
# Check if server has available controls registered
local has_controls=false
if command -v jq &>/dev/null; then
local ctrl_count
ctrl_count=$(echo "$SETTINGS_RESPONSE" | jq -r '.availableControls | length // 0')
if [[ "$ctrl_count" -gt 0 ]]; then
has_controls=true
fi
else
if echo "$SETTINGS_RESPONSE" | grep -q '"availableControls":\[{'; then
has_controls=true
fi
fi
if [[ "$has_controls" == "false" ]]; then
log "No controls registered, discovering and reporting..."
discover_and_report_controls
return
fi
# Apply all values from server dynamically
if command -v jq &>/dev/null; then
local values
values=$(echo "$SETTINGS_RESPONSE" | jq -r '.values // {}')
local keys
keys=$(echo "$values" | jq -r 'keys[]' 2>/dev/null) || true
local applied=0
for ctrl in $keys; do
local value
value=$(echo "$values" | jq -r ".[\"$ctrl\"] // empty")
if [[ -n "$value" && "$value" != "null" ]]; then
if v4l2-ctl -d "$VIDEO_DEVICE" --set-ctrl="${ctrl}=${value}" 2>/dev/null; then
((applied++))
fi
fi
done
log "Applied $applied camera settings from server"
else
# Fallback: grep-based parsing for values
local values_block
values_block=$(echo "$SETTINGS_RESPONSE" | grep -o '"values":{[^}]*}' | sed 's/"values"://')
if [[ -n "$values_block" ]]; then
# Extract key:value pairs
while [[ "$values_block" =~ \"([a-z_]+)\":(-?[0-9]+) ]]; do
local ctrl="${BASH_REMATCH[1]}"
local value="${BASH_REMATCH[2]}"
v4l2-ctl -d "$VIDEO_DEVICE" --set-ctrl="${ctrl}=${value}" 2>/dev/null || true
values_block="${values_block#*${BASH_REMATCH[0]}}"
done
log "Applied camera settings from server"
fi
fi
}
# Only handle settings for fswebcam/v4l2
if [[ "$CAPTURE_METHOD" == "fswebcam" ]]; then
apply_camera_settings
fi
# Capture image
log "Capturing using $CAPTURE_METHOD ($RESOLUTION)"
# Get System Info for Overlay
SYSTEM_HOSTNAME=$(hostname)
# Get first non-loopback IP
SYSTEM_IP=$(hostname -I 2>/dev/null | awk '{print $1}') || SYSTEM_IP="unknown"
if [[ "$CAPTURE_METHOD" == "rpicam-still" ]] || [[ "$CAPTURE_METHOD" == "libcamera-still" ]]; then
WIDTH=$(echo "$RESOLUTION" | cut -d'x' -f1)
HEIGHT=$(echo "$RESOLUTION" | cut -d'x' -f2)
CMD="rpicam-still"
command -v libcamera-still &>/dev/null && CMD="libcamera-still"
# Note: Text overlay on rpicam-still is not as straightforward as fswebcam (requires --post-process-file or similar),
# so we are skipping overlay for now to keep it simple, or we could use imagemagick later if requested.
if ! $CMD -o "$TEMP_FILE" --width "$WIDTH" --height "$HEIGHT" --nopreview --timeout 2000 >/dev/null 2>&1; then
log_error "Capture failed with $CMD"
exit 1
fi
else
# fswebcam logic
if ! fswebcam -d "$VIDEO_DEVICE" -r "$RESOLUTION" -S "$SKIP_FRAMES" \
--title "$CAMERA_ID" \
--subtitle "$SYSTEM_IP" \
--banner-colour '#AA000000' \
--line-colour '#FF000000' \
--text-colour '#FFFFFF' \
--font "sans:24" \
--timestamp "%Y-%m-%d %H:%M:%S" \
"$TEMP_FILE" 2>/dev/null; then
log_error "Capture failed"
exit 1
fi
fi
FILE_SIZE=$(stat -c%s "$TEMP_FILE" 2>/dev/null || echo "unknown")
log "Captured: $TEMP_FILE ($FILE_SIZE bytes)"
# Upload with retry
upload_success=false
attempt=1
while [[ $attempt -le $MAX_RETRIES ]]; do
log "Upload attempt $attempt/$MAX_RETRIES"
HTTP_RESPONSE=$(curl -s -w "\n%{http_code}" \
--max-time "$TIMEOUT" \
-X POST \
-H "X-API-Key: $API_KEY" \
-F "image=@$TEMP_FILE" \
-F "cameraId=$CAMERA_ID" \
"$API_URL" 2>/dev/null) || true
HTTP_CODE=$(echo "$HTTP_RESPONSE" | tail -n1)
RESPONSE_BODY=$(echo "$HTTP_RESPONSE" | sed '$d')
if [[ "$HTTP_CODE" == "200" ]]; then
log "Upload successful (HTTP $HTTP_CODE)"
upload_success=true
break
else
log_error "Upload failed (HTTP $HTTP_CODE): $RESPONSE_BODY"
if [[ $attempt -lt $MAX_RETRIES ]]; then
log "Retrying in $RETRY_DELAY seconds..."
sleep "$RETRY_DELAY"
fi
fi
((attempt++))
done
if [[ "$upload_success" != "true" ]]; then
log_error "All upload attempts failed"
exit 1
fi
log "Done"