diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..56276de --- /dev/null +++ b/AGENTS.md @@ -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 `` 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:/ # 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. diff --git a/README.md b/README.md index 38a2870..6eaec77 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,88 @@ # 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. \ No newline at end of file +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).