# usb-camera-webui A live video feed from a USB webcam, served through a small web UI, running on a Raspberry Pi 5. ## Why this is its own repo There is a sibling project, **`camera-webui`**, doing the same job on a Jetson Orin Nano with a CSI IMX219. This is deliberately *not* that repo, for two reasons: 1. **Two agents, two repos.** A Codex agent is working in `camera-webui` on the Orin. A second agent editing the same files from another machine would collide for reasons that have nothing to do with either one's ability. 2. **The point is measuring the agent.** Both projects exist to see how far Codex + qwen3.6 gets on real hardware. Two independent runs are readable; one shared repo with merge conflicts is not. Merging them later behind a source-selector — one UI, switchable backends — is a reasonable end state. It is just not the starting point. ## Hardware, verified | | | |---|---| | Board | Raspberry Pi 5, Debian 12 (bookworm), aarch64 | | Camera | **Logitech C505 HD Webcam** (`046d:08e3`), USB/UVC | | Node | `/dev/video0` | | Formats | `MJPG` (Motion-JPEG) and `YUYV` (4:2:2) | | Confirmed | 1280x720 MJPG frame captured, a genuine 33 KB JPEG | ```bash v4l2-ctl -d /dev/video0 --list-formats v4l2-ctl -d /dev/video0 --set-fmt-video=width=1280,height=720,pixelformat=MJPG \ --stream-mmap --stream-count=1 --stream-to=/tmp/frame.jpg file /tmp/frame.jpg # must say "JPEG image data", not just exist ``` ⚠ **`/dev/video*` is crowded here.** The Pi 5 exposes many nodes (`/dev/video19`–`35`) belonging to its ISP and codec blocks, present with no camera attached. The webcam is `/dev/video0`; a second node (`/dev/video1`) is the UVC metadata interface, not a capture device. **Never pick a node by index — confirm with `v4l2-ctl --list-devices`.** ## UVC is the portable path This is plain **V4L2**. No libcamera, no `picamera2`, no Argus. That matters beyond this repo: UVC is the one capture path that works identically on the Pi and the Jetson, so a backend written here is the piece most likely to survive being moved. **Prefer `MJPG` over `YUYV` for streaming.** The camera compresses in hardware, so MJPG frames come off the wire ready to serve; `YUYV` is uncompressed and will spend Pi CPU on encoding you did not need to do. ## ⚠ Power is a real constraint here The Pi is running on a **3 A supply, not the 5 A one it wants**: ``` max_current = 3000 mA usb_max_current_enable = 0 # restricted USB budget ``` The C505 fits within that, and the board has been stable with it attached — but the headroom is thin. **Do not assume a second USB device will fit**, and do not add powered peripherals casually. If the board drops out, check these after it returns: ```bash vcgencmd get_throttled # undervoltage bits cat /sys/firmware/devicetree/base/chosen/power/usb_over_current_detected journalctl -b -1 -e # clean shutdown, or abrupt cut? ``` That last one is the useful one: this machine keeps **persistent logs**, so a previous boot ending in an orderly shutdown sequence means something different from one that stops mid-line. ## Planned stages 1. **Capture** — open `/dev/video0`, pull frames, confirm format and rate. *(Hardware already verified; the code is not written.)* 2. **Live feed** — MJPEG over HTTP first. It works in any browser with no negotiation, and the camera already produces the frames. 3. **Web UI** — one page: the feed, plus basic controls. 4. **Later** — resolution/format switching, snapshots, and only then anything heavier. Each stage should be usable on its own before the next begins. ## Development Codex runs on this Pi, so development happens on the target — no cross-compiling, no deploy step. The model endpoint and MCP gateway are on `halogen` and reachable from here by name. See [AGENTS.md](AGENTS.md).