src.nth.io/

summaryrefslogtreecommitdiff
path: root/TESTING.md
diff options
context:
space:
mode:
authorLuke Hoersten <[email protected]>2026-06-13 22:48:48 -0500
committerLuke Hoersten <[email protected]>2026-06-13 22:48:48 -0500
commit6cbdd4ed7466da28e524c9e0804722428a4b9698 (patch)
tree90df296bf9df9ec4be0ad22267fcea51df92fc93 /TESTING.md
parente7feac61e5ea275f303574fedd942ebed8fc8e73 (diff)
M5: POST /frame — hardware JPEG decode + orientation-aware paint
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.
Diffstat (limited to 'TESTING.md')
-rw-r--r--TESTING.md59
1 files changed, 52 insertions, 7 deletions
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://<device-ip>/frame
+
+# Then switch to landscape and re-test with an 800x480 JPEG:
+curl -X POST -H "Content-Type: application/json" \
+ -d '{"orientation":"landscape"}' http://<device-ip>/config
+
+curl -X POST -H "Content-Type: image/jpeg" \
+ --data-binary @test-800x480.jpg \
+ http://<device-ip>/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://<device-ip>/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://<device-ip>/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://<device-ip>/frame
+
+# Concurrent posts — second gets 503:
+curl -X POST -H "Content-Type: image/jpeg" --data-binary @test-480x800.jpg \
+ http://<device-ip>/frame &
+curl -i -X POST -H "Content-Type: image/jpeg" --data-binary @test-480x800.jpg \
+ http://<device-ip>/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://<device-ip>/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).
---