From 132b88337bb04ccf34389e50d07650c759bb33ff Mon Sep 17 00:00:00 2001 From: Claude Worker claude2 Date: Thu, 11 Jun 2026 02:03:13 +0200 Subject: [PATCH] fix(qr): adaptive frame size + OV3660 tuning --- .../components/local_puzzles/qr_puzzle.c | 73 ++++++++++++------- 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/idf_zacus/components/local_puzzles/qr_puzzle.c b/idf_zacus/components/local_puzzles/qr_puzzle.c index caf3fbf..f3e1d9a 100644 --- a/idf_zacus/components/local_puzzles/qr_puzzle.c +++ b/idf_zacus/components/local_puzzles/qr_puzzle.c @@ -64,43 +64,48 @@ static void scan_task(void *arg) { vTaskDelete(NULL); return; } - if (quirc_resize(q, QR_WIDTH, QR_HEIGHT) < 0) { - ESP_LOGE(TAG, "quirc_resize(%d,%d) failed", QR_WIDTH, QR_HEIGHT); - quirc_destroy(q); - esp_camera_deinit(); - s_task = NULL; - vTaskDelete(NULL); - return; - } + // quirc is (re)sized to the camera's ACTUAL frame geometry. The OV3660 + // on this board does not always deliver exactly QVGA — adapt at runtime + // instead of dropping every off-size frame (the old fixed 320x240 guard + // silently starved quirc when the sensor returned another size). + int q_w = 0, q_h = 0; while (s_run) { camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { vTaskDelay(pdMS_TO_TICKS(50)); continue; } - // Guard: only copy if the frame geometry matches what quirc was sized to. - if (fb->width == QR_WIDTH && fb->height == QR_HEIGHT) { - if (s_preview_cb) s_preview_cb(fb->buf, QR_WIDTH, QR_HEIGHT); + if (fb->width != q_w || fb->height != q_h) { + if (quirc_resize(q, fb->width, fb->height) < 0) { + ESP_LOGE(TAG, "quirc_resize(%d,%d) failed", fb->width, fb->height); + esp_camera_fb_return(fb); + vTaskDelay(pdMS_TO_TICKS(50)); + continue; + } + q_w = fb->width; + q_h = fb->height; + ESP_LOGI(TAG, "scanning at sensor geometry %dx%d", q_w, q_h); + } - uint8_t *img = quirc_begin(q, NULL, NULL); - memcpy(img, fb->buf, (size_t)QR_WIDTH * QR_HEIGHT); - quirc_end(q); + if (s_preview_cb) s_preview_cb(fb->buf, q_w, q_h); - int n = quirc_count(q); - for (int i = 0; i < n; i++) { - // static: ~12.5 KB total — too large for the task stack; single-instance task. - static struct quirc_code code; - static struct quirc_data data; - quirc_extract(q, i, &code); - if (quirc_decode(&code, &data) == 0) { - // data.payload is a NUL-terminated uint8_t string for text QRs. - if (seq_validator_feed(&s_seq, (const char *)data.payload)) { - if (s_cb) s_cb(); - s_run = false; - } + uint8_t *img = quirc_begin(q, NULL, NULL); + memcpy(img, fb->buf, (size_t)q_w * q_h); + quirc_end(q); + + int n = quirc_count(q); + for (int i = 0; i < n; i++) { + // static: ~12.5 KB total — too large for the task stack; single-instance task. + static struct quirc_code code; + static struct quirc_data data; + quirc_extract(q, i, &code); + if (quirc_decode(&code, &data) == 0) { + // data.payload is a NUL-terminated uint8_t string for text QRs. + ESP_LOGI(TAG, "QR decoded: %s", (const char *)data.payload); + if (seq_validator_feed(&s_seq, (const char *)data.payload)) { + if (s_cb) s_cb(); + s_run = false; } } - } else { - ESP_LOGW(TAG, "unexpected frame size %dx%d, skipping", fb->width, fb->height); } esp_camera_fb_return(fb); // always return the frame @@ -126,6 +131,18 @@ esp_err_t qr_puzzle_start(const char *const *expected, size_t count, qr_solved_c return e; } + // Tune the OV3660 for QR decoding off a backlit LCD: max contrast + + // sharpness, and cap the gain/exposure so the bright screen does not + // bloom and wash out the QR modules. + sensor_t *s = esp_camera_sensor_get(); + if (s) { + if (s->set_contrast) s->set_contrast(s, 2); + if (s->set_sharpness) s->set_sharpness(s, 2); + if (s->set_gainceiling) s->set_gainceiling(s, GAINCEILING_2X); + if (s->set_whitebal) s->set_whitebal(s, 1); + if (s->set_aec2) s->set_aec2(s, 1); + } + seq_validator_init(&s_seq, expected, count); s_cb = cb; s_run = true;