#!/usr/bin/env bash set -euo pipefail camera_serial="${1:-}" camera_label="${2:-camera}" capture_width="${OMNI_CAMERA_DISCOVERY_WIDTH:-1280}" capture_height="${OMNI_CAMERA_DISCOVERY_HEIGHT:-720}" timeout_sec="${OMNI_CAMERA_DISCOVERY_TIMEOUT_SEC:-10}" die() { echo "[camera-discovery] ${camera_label}: $*" >&2 exit 1 } [[ -n "${camera_serial}" ]] || die "camera serial is required" [[ "${timeout_sec}" =~ ^[0-9]+$ ]] || die "OMNI_CAMERA_DISCOVERY_TIMEOUT_SEC must be a non-negative integer" command -v udevadm >/dev/null 2>&1 || die "missing required command: udevadm" command -v v4l2-ctl >/dev/null 2>&1 || die "missing required command: v4l2-ctl" shopt -s nullglob deadline=$((SECONDS + timeout_sec)) while true; do matches=() serial_nodes=() for device in /dev/video*; do [[ -c "${device}" ]] || continue device_serial="$( udevadm info --query=property --name="${device}" 2>/dev/null \ | sed -n 's/^ID_SERIAL_SHORT=//p' \ | head -1 )" [[ "${device_serial}" == "${camera_serial}" ]] || continue serial_nodes+=("${device}") formats="$(v4l2-ctl -d "${device}" --list-formats-ext 2>/dev/null || true)" grep -q "'MJPG'" <<<"${formats}" || continue grep -q "Size: Discrete ${capture_width}x${capture_height}" <<<"${formats}" || continue matches+=("${device}") done if (( ${#matches[@]} == 1 )); then echo "[camera-discovery] ${camera_label}: serial=${camera_serial} -> ${matches[0]} (MJPG ${capture_width}x${capture_height})" >&2 printf '%s\n' "${matches[0]}" exit 0 fi if (( ${#matches[@]} > 1 )); then die "serial=${camera_serial} matched multiple MJPG nodes: ${matches[*]}" fi if (( SECONDS >= deadline )); then if (( ${#serial_nodes[@]} == 0 )); then die "serial=${camera_serial} was not found under /dev/video*" fi die "serial=${camera_serial} has no MJPG ${capture_width}x${capture_height} node; serial nodes: ${serial_nodes[*]}" fi sleep 0.2 done