#!/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}" 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 "" # Check video device 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" # 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 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 exit 0 fi # ============================================================================== # Main Capture & Upload # ============================================================================== TEMP_FILE="${TEMP_DIR}/picupper_${CAMERA_ID}_$(date +%s).jpg" # Check video device if [[ ! -e "$VIDEO_DEVICE" ]]; then log_error "Video device not found: $VIDEO_DEVICE" exit 1 fi # Fetch and apply camera settings from server apply_camera_settings() { # Check if v4l2-ctl is available if ! command -v v4l2-ctl &>/dev/null; then log "v4l2-ctl not installed, skipping camera settings" return fi # Derive settings URL from upload URL (replace /upload with /settings) 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, using camera defaults" return fi # Check if server has custom settings (look for updatedAt field) local has_custom_settings=false if command -v jq &>/dev/null; then local updated_at updated_at=$(echo "$SETTINGS_RESPONSE" | jq -r '.settings.updatedAt // empty') if [[ -n "$updated_at" && "$updated_at" != "null" ]]; then has_custom_settings=true fi else if echo "$SETTINGS_RESPONSE" | grep -q '"updatedAt"'; then has_custom_settings=true fi fi if [[ "$has_custom_settings" == "false" ]]; then # No custom settings on server - upload current camera settings log "No settings on server, uploading current camera settings..." upload_current_settings return fi # Apply settings from server if command -v jq &>/dev/null; then local settings settings=$(echo "$SETTINGS_RESPONSE" | jq -r '.settings // empty') if [[ -n "$settings" ]]; then for ctrl in focus_automatic_continuous focus_absolute exposure_auto exposure_absolute brightness contrast; do local value value=$(echo "$settings" | jq -r ".$ctrl // empty") if [[ -n "$value" && "$value" != "null" ]]; then v4l2-ctl -d "$VIDEO_DEVICE" --set-ctrl="${ctrl}=${value}" 2>/dev/null || true fi done log "Applied camera settings from server" fi else for ctrl in focus_automatic_continuous focus_absolute exposure_auto exposure_absolute brightness contrast; do local value value=$(echo "$SETTINGS_RESPONSE" | grep -o "\"${ctrl}\":[0-9-]*" | cut -d: -f2) if [[ -n "$value" ]]; then v4l2-ctl -d "$VIDEO_DEVICE" --set-ctrl="${ctrl}=${value}" 2>/dev/null || true fi done log "Applied camera settings from server" fi } # Read current camera settings and upload to server upload_current_settings() { local SETTINGS_URL="${API_URL%/upload}/settings/$CAMERA_ID" # Read current values from camera local focus_auto focus_abs exp_auto exp_abs brightness contrast focus_auto=$(v4l2-ctl -d "$VIDEO_DEVICE" --get-ctrl=focus_automatic_continuous 2>/dev/null | cut -d: -f2 | tr -d ' ') || focus_auto="" focus_abs=$(v4l2-ctl -d "$VIDEO_DEVICE" --get-ctrl=focus_absolute 2>/dev/null | cut -d: -f2 | tr -d ' ') || focus_abs="" exp_auto=$(v4l2-ctl -d "$VIDEO_DEVICE" --get-ctrl=exposure_auto 2>/dev/null | cut -d: -f2 | tr -d ' ') || exp_auto="" exp_abs=$(v4l2-ctl -d "$VIDEO_DEVICE" --get-ctrl=exposure_absolute 2>/dev/null | cut -d: -f2 | tr -d ' ') || exp_abs="" brightness=$(v4l2-ctl -d "$VIDEO_DEVICE" --get-ctrl=brightness 2>/dev/null | cut -d: -f2 | tr -d ' ') || brightness="" contrast=$(v4l2-ctl -d "$VIDEO_DEVICE" --get-ctrl=contrast 2>/dev/null | cut -d: -f2 | tr -d ' ') || contrast="" # Build JSON payload local json_parts=() [[ -n "$focus_auto" ]] && json_parts+=("\"focus_automatic_continuous\": $focus_auto") [[ -n "$focus_abs" ]] && json_parts+=("\"focus_absolute\": $focus_abs") [[ -n "$exp_auto" ]] && json_parts+=("\"exposure_auto\": $exp_auto") [[ -n "$exp_abs" ]] && json_parts+=("\"exposure_absolute\": $exp_abs") [[ -n "$brightness" ]] && json_parts+=("\"brightness\": $brightness") [[ -n "$contrast" ]] && json_parts+=("\"contrast\": $contrast") if [[ ${#json_parts[@]} -eq 0 ]]; then log "Could not read camera settings" return fi # Join with commas local IFS=',' local json_body="{${json_parts[*]}}" # Upload to server local response response=$(curl -s --max-time 10 \ -X PUT \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d "$json_body" \ "$SETTINGS_URL" 2>/dev/null) || true if echo "$response" | grep -q '"success"'; then log "Uploaded current camera settings to server" else log "Failed to upload camera settings" fi } apply_camera_settings # Capture image log "Capturing from $VIDEO_DEVICE ($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 ! 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 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"