From 6cbdd4ed7466da28e524c9e0804722428a4b9698 Mon Sep 17 00:00:00 2001 From: Luke Hoersten Date: Sat, 13 Jun 2026 22:48:48 -0500 Subject: M5: POST /frame — hardware JPEG decode + orientation-aware paint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jpeg_decoder.{h,c} wraps esp_driver_jpeg (ESP32-P4 hardware decoder). - One-time engine + DMA-aligned PSRAM scratch buffer setup (1 MB input, 768 KB output @ panel native 800x480 RGB565). - try_lock + unlock so concurrent /frame POSTs get 503 instead of queueing, per spec. - jpeg_decoder_get_info() reports the dimensions; the http handler validates them against the effective resolution before painting. display.h adds display_present_rgb565(src, w, h): - Landscape: src is 800x480, memcpy 1:1 into the panel framebuffer. - Portrait: src is 480x800, software rotate 90° CW into the 800x480 panel framebuffer. (PPA / 2D-DMA hardware rotation is a later optimization if portrait latency matters.) http_api.c adds POST /frame: - Content-Type must be image/jpeg → else 400. - Empty body → 400. > 1 MB → 413. Display not initialized → 500. - jpeg_decoder_try_lock(0) for concurrency: second post returns 503. - Body streamed into the decoder's input buffer in chunks. - Decode failure or dimension mismatch → 400 + decode_errors++. - Paint failure → 500. - Success → frames_received++, last_frame_us = esp_timer_get_time(), 204 No Content. app_main initializes the JPEG decoder after display_init(). Both are best-effort: failures log a warning and leave the rest of the firmware running. CMakeLists.txt: add esp_driver_jpeg to REQUIRES. Known gap (M6 closes it): /frame currently paints regardless of wake/sleep state. The 409-when-asleep rule lands with POST /state in M6. Build clean against ESP-IDF 5.4 (binary ~640 KB). TESTING.md M5 expanded with portrait/landscape test commands, ImageMagick test-image recipes, the full negative matrix (wrong Content-Type, oversize, wrong dims, concurrent, garbage), and the M6 dependency note. --- TESTING.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 7 deletions(-) (limited to 'TESTING.md') diff --git a/TESTING.md b/TESTING.md index dd17d94..e5f156c 100644 --- a/TESTING.md +++ b/TESTING.md @@ -201,25 +201,70 @@ Side-effects to confirm: ## M5 — JPEG Frame Push -**Acceptance**: `POST /frame` paints an 800x480 (or 480x800 portrait) JPEG. +**Acceptance**: `POST /frame` paints a 480x800 (portrait, default) or 800x480 (landscape) JPEG. Orientation determines the expected dimensions and the panel-side rotation. **How to verify** ```bash -# After /config sets state=wake (M6 dep — for now test post-/wake or hardcode awake): +# Default orientation (portrait) — send 480x800: curl -X POST -H "Content-Type: image/jpeg" \ --data-binary @test-480x800.jpg \ http:///frame + +# Then switch to landscape and re-test with an 800x480 JPEG: +curl -X POST -H "Content-Type: application/json" \ + -d '{"orientation":"landscape"}' http:///config + +curl -X POST -H "Content-Type: image/jpeg" \ + --data-binary @test-800x480.jpg \ + http:///frame ``` -Visual: matching pattern appears on the panel. Re-test in landscape orientation with `test-800x480.jpg`. +Visual: image fills the panel correctly oriented. After paint, `GET /state` shows `frames_received` incremented and `last_frame_ms_ago` populated. + +Generate test JPEGs with ImageMagick: + +```bash +# 8 vertical color bars at 480x800 portrait: +convert -size 60x800 gradient:white-black -duplicate 7 +append test-480x800.jpg +# or just any 480x800 JPEG: +convert -size 480x800 plasma: test-480x800.jpg +convert -size 800x480 plasma: test-800x480.jpg +``` Negative tests: -- Non-JPEG body → 400. -- 1.1 MB body → 413. -- 640x480 body (wrong size) → 400 or visible distortion (depends on decoder strictness). -**Status**: ⬜ pending. +```bash +# Wrong Content-Type — expect 400: +curl -i -X POST -H "Content-Type: image/png" --data-binary @anything \ + http:///frame + +# Oversize — expect 413: +dd if=/dev/urandom of=big.bin bs=1M count=2 +curl -i -X POST -H "Content-Type: image/jpeg" --data-binary @big.bin \ + http:///frame + +# Wrong dimensions — expect 400 with "expected WxH, got WxH": +convert -size 640x480 plasma: test-640x480.jpg +curl -i -X POST -H "Content-Type: image/jpeg" --data-binary @test-640x480.jpg \ + http:///frame + +# Concurrent posts — second gets 503: +curl -X POST -H "Content-Type: image/jpeg" --data-binary @test-480x800.jpg \ + http:///frame & +curl -i -X POST -H "Content-Type: image/jpeg" --data-binary @test-480x800.jpg \ + http:///frame # expect 503 if first is still in flight + +# Garbage bytes claiming to be JPEG — expect 400: +curl -i -X POST -H "Content-Type: image/jpeg" -d 'not a jpeg' \ + http:///frame +``` + +After every error: `decode_errors` in `GET /state` should increment. + +**Known gap (M6 closes this)**: M5 paints regardless of wake/sleep state. The `/frame` → `409` when asleep rule is added with `POST /state` in M6. Until then, a configured device boots ASLEEP per spec but `/frame` still paints because the state guard isn't wired yet. + +**Status**: 🟡 builds clean against ESP-IDF 5.4. Awaiting hardware verification (the test pattern from M3 confirms paint works; M5 adds the JPEG path). --- -- cgit v1.2.3