feat: ESP32-CAM emulation with webcam frame bridge
First open-source end-to-end emulation of the AI-Thinker ESP32-CAM
in QEMU, paired with a browser webcam → firmware bridge so users can
develop camera sketches without hardware. Status: esp_camera_init()
returns ESP_OK; OV2640 chip-id verifies (PID/VER/MIDH/MIDL exactly
match the datasheet); GPIO 25 VSYNC NEGEDGE interrupt enabled by
the upstream driver. Final piece (cam_task accepting frames) is in
progress — descriptor walker fix landed in this commit.
Backend (Python/FastAPI):
- simulation.py: camera_attach/frame/detach WS handlers
- esp32_worker.py: ctypes binding to velxio_push_camera_frame +
feature-detection fallback for older DLLs
- esp32_lib_manager.py: forward camera commands to the worker stdin
- esp-idf-template/main/CMakeLists.txt: esp32-camera headers added
via add_prebuilt_library + REQUIRES driver (resolves i2c_master_*
symbols). LED_BUILTIN=2 fallback for sketches that hardcode it.
Frontend (React/TS):
- EditorToolbar.tsx: ESP32-CAM (and the rest of the ESP32 family)
added to isQemuBoard list — Run button now starts the QEMU bridge
for these boards instead of falling through to the AVR path
- useWebcamFrames.ts: getUserMedia → OffscreenCanvas →
toBlob('image/jpeg') → base64 → WS at ~10 fps
- CameraToggle.tsx: header button with status colors + frame counter
- SimulatorCanvas.tsx: render CameraToggle for esp32-cam boards
- Esp32Bridge.ts: sendCameraAttach/Frame/Detach + chunked btoa
- useSimulatorStore.ts: diagnostic log on compileBoardProgram
- components-metadata.json: regen including esp32-cam component
Submodule pointer:
- wokwi-libs/qemu-lcgamboa → ff8eee0 (camera devices commit on
davidmonterocrespo24/qemu-lcgamboa branch picsimlab-esp32)
Investigation + tests in test/test-esp32-cam/:
- 13 autosearch markdown docs (overview, SOTA, OV2640 spec, DVP/I2S
spec, build blueprint, blockers resolved, descriptor walker fix)
- 5 sketches (camera_init, sccb_probe, dma_smoke, frame_roundtrip,
webcam_demo) + 8 live + WS regression tests
- README with the user-facing flow
.gitignore:
- libqemu-*.dll.{pre-camera,new,bak} (rollback points, regenerated)
- wokwi-libs/esp32-camera/ (clone consumed by arduino-esp32 path,
not part of this repo)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 05:28:55 +07:00
|
|
|
# test-esp32-cam — emulating the OV2640 over QEMU, faithful
|
|
|
|
|
|
|
|
|
|
Phase plan for emulating the ESP32-CAM camera (OV2640 + DVP + I²S +
|
|
|
|
|
DMA) as **real QEMU peripherals** — no library shim, no fakery. The
|
|
|
|
|
upstream `espressif/esp32-camera` driver runs unmodified.
|
|
|
|
|
|
|
|
|
|
## Reading order
|
|
|
|
|
|
|
|
|
|
1. `autosearch/00_overview.md` — problem statement and the three
|
|
|
|
|
candidate paths (we picked Path B: real peripherals).
|
|
|
|
|
2. `autosearch/01_state_of_the_art.md` — what other projects do.
|
|
|
|
|
3. `autosearch/02_qemu_lcgamboa_audit.md` — confirms our QEMU fork
|
|
|
|
|
has *no* camera/DVP/I²S today; lists every ESP32 device it does
|
|
|
|
|
ship.
|
|
|
|
|
4. `autosearch/03_browser_webcam_capture.md` — `getUserMedia` →
|
|
|
|
|
canvas → JPEG → WebSocket plumbing.
|
|
|
|
|
5. `autosearch/04_proposed_architecture.md` — end-to-end pipeline.
|
|
|
|
|
6. `autosearch/05_open_questions.md` — design decisions still open.
|
|
|
|
|
7. `autosearch/06_existing_test_patterns.md` — how the DHT22 / HC-SR04
|
|
|
|
|
live tests work; we mirror them.
|
|
|
|
|
8. `autosearch/07_ov2640_sccb_spec.md` — minimum register set the
|
|
|
|
|
QEMU OV2640 device must implement.
|
|
|
|
|
9. `autosearch/08_dvp_i2s_spec.md` — exact I²S0 register sequence the
|
|
|
|
|
esp32-camera driver issues, and the `lldesc_t` DMA descriptor
|
|
|
|
|
format.
|
|
|
|
|
10. `autosearch/09_qemu_build_blueprint.md` — how a Phase-2 patch in
|
refactor: rename wokwi-libs/ → third-party/
The directory grew well beyond Wokwi-only contents: it now hosts
lcgamboa's QEMU fork (qemu-lcgamboa), Espressif's esp32-camera, the
ngspice WASM build, fritzing-parts, picowi, an alternative QEMU
(qemu-esp32), the 100_Days_100_IoT_Projects examples repo, and
Wokwi's own avr8js/rp2040js/wokwi-elements/wokwi-features/wokwi-boards.
"wokwi-libs" was misleading — half the contents have nothing to do
with Wokwi. "third-party/" is the standard convention for vendored
external dependencies.
Mechanical changes:
Path rename:
wokwi-libs/ → third-party/
update-wokwi-libs.bat → update-third-party.bat
docs/WOKWI_LIBS.md → docs/THIRD_PARTY.md
Submodule reconfiguration:
.gitmodules — 4 path= and section names updated
.git/modules/wokwi-libs/ → .git/modules/third-party/
each submodule's .git file rewired to ../../.git/modules/third-party/<name>
Reference updates (~80 files): vite.config.ts aliases, Dockerfile
COPY paths, GH Actions workflow steps, build_qemu_*.sh, all
docs/* and test/*/autosearch/* entries that mention the path,
package-lock.json file: dependencies, .gitignore patterns,
sitemap.xml + index.html SEO blurbs, scripts/generate-component-*,
.dockerignore, .idea/vcs.xml. Bulk replaced both `wokwi-libs/`
(path) and bare `wokwi-libs` (textual mentions in docs/comments).
Verified:
- npx tsc -b --noEmit produces no new errors related to these paths
- vite.config.ts aliases now point at ../third-party/avr8js etc.
- All 4 git submodules (avr8js, rp2040js, wokwi-elements,
wokwi-features) are linked under third-party/ with their
worktrees re-populated and config files referencing the new path
- `grep -r wokwi-libs` returns zero hits outside node_modules,
.vite, frontend/dist, third-party/ (upstream submodule contents),
*.pyc caches, and *.dll.pre-camera rollback binaries
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 10:58:57 +07:00
|
|
|
`third-party/qemu-lcgamboa/` reaches a running container.
|
feat: ESP32-CAM emulation with webcam frame bridge
First open-source end-to-end emulation of the AI-Thinker ESP32-CAM
in QEMU, paired with a browser webcam → firmware bridge so users can
develop camera sketches without hardware. Status: esp_camera_init()
returns ESP_OK; OV2640 chip-id verifies (PID/VER/MIDH/MIDL exactly
match the datasheet); GPIO 25 VSYNC NEGEDGE interrupt enabled by
the upstream driver. Final piece (cam_task accepting frames) is in
progress — descriptor walker fix landed in this commit.
Backend (Python/FastAPI):
- simulation.py: camera_attach/frame/detach WS handlers
- esp32_worker.py: ctypes binding to velxio_push_camera_frame +
feature-detection fallback for older DLLs
- esp32_lib_manager.py: forward camera commands to the worker stdin
- esp-idf-template/main/CMakeLists.txt: esp32-camera headers added
via add_prebuilt_library + REQUIRES driver (resolves i2c_master_*
symbols). LED_BUILTIN=2 fallback for sketches that hardcode it.
Frontend (React/TS):
- EditorToolbar.tsx: ESP32-CAM (and the rest of the ESP32 family)
added to isQemuBoard list — Run button now starts the QEMU bridge
for these boards instead of falling through to the AVR path
- useWebcamFrames.ts: getUserMedia → OffscreenCanvas →
toBlob('image/jpeg') → base64 → WS at ~10 fps
- CameraToggle.tsx: header button with status colors + frame counter
- SimulatorCanvas.tsx: render CameraToggle for esp32-cam boards
- Esp32Bridge.ts: sendCameraAttach/Frame/Detach + chunked btoa
- useSimulatorStore.ts: diagnostic log on compileBoardProgram
- components-metadata.json: regen including esp32-cam component
Submodule pointer:
- wokwi-libs/qemu-lcgamboa → ff8eee0 (camera devices commit on
davidmonterocrespo24/qemu-lcgamboa branch picsimlab-esp32)
Investigation + tests in test/test-esp32-cam/:
- 13 autosearch markdown docs (overview, SOTA, OV2640 spec, DVP/I2S
spec, build blueprint, blockers resolved, descriptor walker fix)
- 5 sketches (camera_init, sccb_probe, dma_smoke, frame_roundtrip,
webcam_demo) + 8 live + WS regression tests
- README with the user-facing flow
.gitignore:
- libqemu-*.dll.{pre-camera,new,bak} (rollback points, regenerated)
- wokwi-libs/esp32-camera/ (clone consumed by arduino-esp32 path,
not part of this repo)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 05:28:55 +07:00
|
|
|
|
|
|
|
|
## Reference sources cloned into the tree
|
|
|
|
|
|
refactor: rename wokwi-libs/ → third-party/
The directory grew well beyond Wokwi-only contents: it now hosts
lcgamboa's QEMU fork (qemu-lcgamboa), Espressif's esp32-camera, the
ngspice WASM build, fritzing-parts, picowi, an alternative QEMU
(qemu-esp32), the 100_Days_100_IoT_Projects examples repo, and
Wokwi's own avr8js/rp2040js/wokwi-elements/wokwi-features/wokwi-boards.
"wokwi-libs" was misleading — half the contents have nothing to do
with Wokwi. "third-party/" is the standard convention for vendored
external dependencies.
Mechanical changes:
Path rename:
wokwi-libs/ → third-party/
update-wokwi-libs.bat → update-third-party.bat
docs/WOKWI_LIBS.md → docs/THIRD_PARTY.md
Submodule reconfiguration:
.gitmodules — 4 path= and section names updated
.git/modules/wokwi-libs/ → .git/modules/third-party/
each submodule's .git file rewired to ../../.git/modules/third-party/<name>
Reference updates (~80 files): vite.config.ts aliases, Dockerfile
COPY paths, GH Actions workflow steps, build_qemu_*.sh, all
docs/* and test/*/autosearch/* entries that mention the path,
package-lock.json file: dependencies, .gitignore patterns,
sitemap.xml + index.html SEO blurbs, scripts/generate-component-*,
.dockerignore, .idea/vcs.xml. Bulk replaced both `wokwi-libs/`
(path) and bare `wokwi-libs` (textual mentions in docs/comments).
Verified:
- npx tsc -b --noEmit produces no new errors related to these paths
- vite.config.ts aliases now point at ../third-party/avr8js etc.
- All 4 git submodules (avr8js, rp2040js, wokwi-elements,
wokwi-features) are linked under third-party/ with their
worktrees re-populated and config files referencing the new path
- `grep -r wokwi-libs` returns zero hits outside node_modules,
.vite, frontend/dist, third-party/ (upstream submodule contents),
*.pyc caches, and *.dll.pre-camera rollback binaries
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 10:58:57 +07:00
|
|
|
`third-party/esp32-camera/` (Apache 2.0, cloned for offline
|
feat: ESP32-CAM emulation with webcam frame bridge
First open-source end-to-end emulation of the AI-Thinker ESP32-CAM
in QEMU, paired with a browser webcam → firmware bridge so users can
develop camera sketches without hardware. Status: esp_camera_init()
returns ESP_OK; OV2640 chip-id verifies (PID/VER/MIDH/MIDL exactly
match the datasheet); GPIO 25 VSYNC NEGEDGE interrupt enabled by
the upstream driver. Final piece (cam_task accepting frames) is in
progress — descriptor walker fix landed in this commit.
Backend (Python/FastAPI):
- simulation.py: camera_attach/frame/detach WS handlers
- esp32_worker.py: ctypes binding to velxio_push_camera_frame +
feature-detection fallback for older DLLs
- esp32_lib_manager.py: forward camera commands to the worker stdin
- esp-idf-template/main/CMakeLists.txt: esp32-camera headers added
via add_prebuilt_library + REQUIRES driver (resolves i2c_master_*
symbols). LED_BUILTIN=2 fallback for sketches that hardcode it.
Frontend (React/TS):
- EditorToolbar.tsx: ESP32-CAM (and the rest of the ESP32 family)
added to isQemuBoard list — Run button now starts the QEMU bridge
for these boards instead of falling through to the AVR path
- useWebcamFrames.ts: getUserMedia → OffscreenCanvas →
toBlob('image/jpeg') → base64 → WS at ~10 fps
- CameraToggle.tsx: header button with status colors + frame counter
- SimulatorCanvas.tsx: render CameraToggle for esp32-cam boards
- Esp32Bridge.ts: sendCameraAttach/Frame/Detach + chunked btoa
- useSimulatorStore.ts: diagnostic log on compileBoardProgram
- components-metadata.json: regen including esp32-cam component
Submodule pointer:
- wokwi-libs/qemu-lcgamboa → ff8eee0 (camera devices commit on
davidmonterocrespo24/qemu-lcgamboa branch picsimlab-esp32)
Investigation + tests in test/test-esp32-cam/:
- 13 autosearch markdown docs (overview, SOTA, OV2640 spec, DVP/I2S
spec, build blueprint, blockers resolved, descriptor walker fix)
- 5 sketches (camera_init, sccb_probe, dma_smoke, frame_roundtrip,
webcam_demo) + 8 live + WS regression tests
- README with the user-facing flow
.gitignore:
- libqemu-*.dll.{pre-camera,new,bak} (rollback points, regenerated)
- wokwi-libs/esp32-camera/ (clone consumed by arduino-esp32 path,
not part of this repo)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 05:28:55 +07:00
|
|
|
reference). Used by autosearch and the C-side QEMU device once we
|
|
|
|
|
ship Phase 2. Treat as read-only — never modify.
|
|
|
|
|
|
|
|
|
|
## Phase plan
|
|
|
|
|
|
|
|
|
|
| Phase | What lands | Status | Validating sketch / test |
|
|
|
|
|
|-------|----------------------------------------------------------------|----------|--------------------------|
|
|
|
|
|
| 0 | Research + tests skeleton | done | static metadata + WS-mock |
|
|
|
|
|
| 1 | `hw/i2c/esp32_ov2640.c` — SCCB chip-id | **PASS** | `test_sccb_probe_live.py` |
|
|
|
|
|
| 2 | `hw/misc/esp32_i2s_cam.c` — DMA + EOF | **PASS** | `test_dma_smoke_live.py` |
|
|
|
|
|
| 3a | Host frame injection (webcam → backend → ctypes → QEMU → buf) | **PASS** | `test_frame_roundtrip_live.py` |
|
|
|
|
|
| 3b | Upstream `esp_camera_init` + `fb_get` round-trip | xfail | `test_camera_live.py` (see autosearch/10) |
|
|
|
|
|
| 4 | CI build matrix + GH release upload | TODO | (build only) |
|
|
|
|
|
| 5 | Frontend: webcam hook + Camera button + missing pins | TODO | manual smoke |
|
|
|
|
|
|
|
|
|
|
## Layout
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
test-esp32-cam/
|
|
|
|
|
├── README.md ← you are here
|
|
|
|
|
├── autosearch/ ← public-internet research
|
|
|
|
|
├── prototypes/ ← standalone validation runners
|
|
|
|
|
│ ├── echo_server.py ← tiny WS echo for the HTML below
|
|
|
|
|
│ └── webcam_capture.html ← getUserMedia → JPEG → WS prototype
|
|
|
|
|
├── sketches/
|
|
|
|
|
│ ├── camera_init/ ← Phase-3 reproducer (full upstream API)
|
|
|
|
|
│ ├── sccb_probe/ ← Phase-1 reproducer (I²C only, no I²S)
|
|
|
|
|
│ └── dma_smoke/ ← Phase-2 reproducer (raw I²S+DMA poke)
|
|
|
|
|
└── tests/
|
|
|
|
|
├── test_camera_metadata.py ← static + frontend
|
|
|
|
|
├── test_camera_websocket.py ← in-process WS mock
|
|
|
|
|
├── test_camera_live.py ← upstream esp_camera API (xfail, see autosearch/10)
|
|
|
|
|
├── test_sccb_probe_live.py ← Phase 1 PASS
|
|
|
|
|
├── test_dma_smoke_live.py ← Phase 2 PASS
|
|
|
|
|
├── test_frame_roundtrip_live.py ← Phase 3 e2e webcam → fb buffer PASS
|
|
|
|
|
└── webcam_helper.py ← OpenCV / PIL / synthetic JPEG source
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Running
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Static + WS-mock layers (always green, no backend needed)
|
|
|
|
|
python -m pytest test/test-esp32-cam/tests -v
|
|
|
|
|
|
|
|
|
|
# Full live suite — needs a running backend with the QEMU library
|
|
|
|
|
# rebuilt to include the new peripherals.
|
|
|
|
|
cd backend && uvicorn app.main:app --port 8001 &
|
|
|
|
|
VELXIO_BACKEND_URL=http://localhost:8001 \
|
|
|
|
|
python -m pytest test/test-esp32-cam/tests -v
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The three `*_live.py` files all start as `@expectedFailure`. As each
|
|
|
|
|
phase lands, the matching live test gets its decorator flipped off in
|
|
|
|
|
the same diff that lands the QEMU change. That's how the test suite
|
|
|
|
|
tracks emulation progress phase-by-phase.
|
|
|
|
|
|
|
|
|
|
## Webcam → backend prototype (no QEMU)
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
pip install websockets
|
|
|
|
|
python test/test-esp32-cam/prototypes/echo_server.py &
|
|
|
|
|
# then open test/test-esp32-cam/prototypes/webcam_capture.html in a browser
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Validates the browser-side path described in `autosearch/03` without
|
|
|
|
|
needing the backend or QEMU. If frame counter climbs and the preview
|
|
|
|
|
panel updates, the transport is good.
|