Document the USB webcam target, verified on the hardware
Its own repo rather than a source-selector inside camera-webui: a Codex agent is already working in that repo on the Orin, and a second agent editing the same files from another machine would collide for reasons unrelated to either one's ability. Both projects exist to measure Codex + qwen3.6 on real hardware, and two independent runs are readable where one repo full of merge conflicts is not. Merging behind a switchable backend later is still a reasonable end state. Hardware facts are measured, not assumed: a Logitech C505 (046d:08e3) on /dev/video0 offering MJPG and YUYV, with a 1280x720 MJPG capture confirmed as a genuine 33 KB JPEG by file(1) rather than by the write succeeding. ⚠ Records that /dev/video* is crowded on this board — video19 through video35 belong to the ISP and codec blocks with no camera attached, and video1 is the webcam's metadata interface. Selecting a node by index is the obvious mistake here, so AGENTS.md forbids it and names --list-devices and the USB ID instead. Power is written in as a constraint rather than a footnote. The board runs on a 3 A supply with usb_max_current_enable=0, so the USB budget is the restricted one. The C505 fits and the board is stable, but a second device may not, and the post-mortem commands are recorded because this machine now keeps persistent logs: a previous boot that ends in an orderly shutdown means something different from one that stops mid-line. AGENTS.md carries the rules earned elsewhere tonight: write files with apply_patch and never write_stdin, stop after two identical failures instead of incrementing an identifier, emit tool calls as JSON arguments, and read the working tree with the shell because the gitea MCP tools see the server's copy and cannot see anything uncommitted.
This commit is contained in:
@@ -0,0 +1,91 @@
|
|||||||
|
# Working in this repo
|
||||||
|
|
||||||
|
Project instructions for Codex on `pi5`. The client-wide prompt lives in `~/.codex/AGENTS.md`; this
|
||||||
|
file is the project-specific part.
|
||||||
|
|
||||||
|
## Where you are
|
||||||
|
|
||||||
|
**You are on the target hardware.** `pi5` is the Raspberry Pi 5 this runs on, with the webcam
|
||||||
|
physically attached. You can test everything for real — there is no deploy step and no emulation.
|
||||||
|
Use that: prefer running the thing over reasoning about whether it would run.
|
||||||
|
|
||||||
|
`platform_info` confirms which machine you are on if you need it.
|
||||||
|
|
||||||
|
⚠ **Do not copy code or assumptions from the sibling `camera-webui` repo.** That one targets a Jetson
|
||||||
|
Orin Nano with a CSI IMX219 and uses a different capture stack entirely. Here it is plain **V4L2 /
|
||||||
|
UVC** — no libcamera, no `picamera2`, no Argus, no device-tree overlays.
|
||||||
|
|
||||||
|
## The camera
|
||||||
|
|
||||||
|
**Logitech C505 HD Webcam at `/dev/video0`**, offering `MJPG` and `YUYV`. A 1280x720 MJPG capture is
|
||||||
|
verified working.
|
||||||
|
|
||||||
|
⚠ **Never select a video node by index.** This board exposes `/dev/video19`–`/dev/video35` for its
|
||||||
|
ISP and codec blocks with no camera involved, and `/dev/video1` is the webcam's *metadata* interface,
|
||||||
|
not a capture device. Resolve the device with `v4l2-ctl --list-devices`, or by matching the USB ID
|
||||||
|
`046d:08e3` — never by assuming `video0` is stable across reboots or a second camera being plugged in.
|
||||||
|
|
||||||
|
**Prefer `MJPG` for streaming.** The camera compresses in hardware; `YUYV` is uncompressed and makes
|
||||||
|
the Pi's CPU do encoding work that is already done for you.
|
||||||
|
|
||||||
|
## ⚠ Writing files, and not looping
|
||||||
|
|
||||||
|
**Write files with `apply_patch`**, or a heredoc through the shell. **Never** use `write_stdin` to
|
||||||
|
create file contents — it writes to the stdin of an already-running process and will not create
|
||||||
|
anything. `write_stdin` takes a session id that an actual `exec_command` returned; never invent one
|
||||||
|
and never increment one.
|
||||||
|
|
||||||
|
**If the same tool call fails twice with the same error, STOP and report it.** Do not retry, and do
|
||||||
|
not vary an identifier hoping one works. A repeated identical failure means the assumption is wrong,
|
||||||
|
not that the call needs another attempt.
|
||||||
|
|
||||||
|
This is from life, in the sibling repo: an agent tried to write a file with `write_stdin`, was told
|
||||||
|
`Unknown process id 45`, and answered by incrementing the id — 46, 47, 48 — for **27 attempts over
|
||||||
|
nine minutes** until a human killed it.
|
||||||
|
|
||||||
|
⚠ **Emit tool calls as the API's JSON arguments only.** Markup like `<parameter=...>` inside a string
|
||||||
|
argument means you are mixing in a different tool-calling format, and the call will not do what you
|
||||||
|
mean.
|
||||||
|
|
||||||
|
## ⚠ Read the working tree with the shell, not over MCP
|
||||||
|
|
||||||
|
The `gitea__*` tools read the **Gitea server's** copy of a repo over its API. They do not see this
|
||||||
|
machine's files. Anything uncommitted or unpushed is invisible to them, so a file fetched that way
|
||||||
|
can be silently stale.
|
||||||
|
|
||||||
|
**Use the shell for files in front of you.** Use the MCP tools only for repo state you have not
|
||||||
|
cloned — history, issues, another repository.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- **Power is tight.** The Pi is on a 3 A supply with a restricted USB budget
|
||||||
|
(`usb_max_current_enable=0`). The webcam fits; a second USB device may not. Do not add powered
|
||||||
|
peripherals, and do not assume headroom.
|
||||||
|
- **Install from system packages where possible** (`apt`), not pip, for anything binding to system
|
||||||
|
libraries. Keep dependencies few — they all have to build on aarch64.
|
||||||
|
- **Do not commit captured images or video.** Frames of a real room are not test fixtures. Generate a
|
||||||
|
synthetic one if a fixture is genuinely needed.
|
||||||
|
- This is a **4-core Pi 5 with no GPU acceleration for inference.** Anything per-frame must be cheap,
|
||||||
|
and must run off the capture thread.
|
||||||
|
|
||||||
|
## Verifying your work
|
||||||
|
|
||||||
|
Claims about the camera or the stream need a command that ran:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
v4l2-ctl --list-devices # which node is really the webcam
|
||||||
|
v4l2-ctl -d /dev/video0 --list-formats # what it can actually produce
|
||||||
|
curl -sI http://localhost:<port>/ # is the server responding
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠ **The absence of an error is not evidence that something works.** A stream endpoint returning 200
|
||||||
|
with no frames is indistinguishable from a working one unless you look at what came back. A capture
|
||||||
|
that writes a file proves nothing until the file is checked — `file frame.jpg` must say
|
||||||
|
`JPEG image data`, not merely exist with a non-zero size.
|
||||||
|
|
||||||
|
## Git
|
||||||
|
|
||||||
|
The remote is Gitea at `192.168.2.199:3005`, over SSH on port 2222.
|
||||||
|
|
||||||
|
⚠ **`tea` (the Gitea CLI) is denied by policy.** Ordinary `git` is fine. **Do not push** unless the
|
||||||
|
operator asks — commit locally and say what is ready.
|
||||||
@@ -1,3 +1,88 @@
|
|||||||
# usb-camera-webui
|
# usb-camera-webui
|
||||||
|
|
||||||
USB/UVC webcam live feed + web UI on a Raspberry Pi 5. Deliberately separate from camera-webui (Orin/IMX219) so two Codex agents can work without colliding.
|
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).
|
||||||
|
|||||||
Reference in New Issue
Block a user