Merge qr fixes: adaptive geometry + mirror + QVGA

This commit is contained in:
Claude Worker claude2
2026-06-11 02:56:47 +02:00
+60 -28
View File
@@ -46,6 +46,10 @@ static camera_config_t cam_cfg(void) {
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_GRAYSCALE,
// QVGA: matches the on-device viewfinder canvas (320x240) so the live
// preview keeps working. VGA was tried for QR decode but quirc still
// found zero finder patterns (the LCD-emissive contrast is the real
// blocker, not resolution), and it broke the viewfinder — so revert.
.frame_size = FRAMESIZE_QVGA, // 320x240
.fb_count = 1,
.fb_location = CAMERA_FB_IN_PSRAM,
@@ -64,43 +68,54 @@ 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);
// Diagnostic: a code IDENTIFIED but not decoded points at quiet-zone /
// image-quality issues rather than framing. Throttled to ~1/sec.
static int s_diag_ticks;
if (n > 0 && (s_diag_ticks++ % 33) == 0) {
ESP_LOGI(TAG, "quirc identified %d candidate(s)", n);
}
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 +141,23 @@ 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);
// The OV3660 mounts mirrored on this board: un-mirror so the captured
// image matches reality (the viewfinder showed a mirror image, and a
// flipped frame defeats QR finder-pattern detection).
if (s->set_hmirror) s->set_hmirror(s, 1);
if (s->set_vflip) s->set_vflip(s, 1);
}
seq_validator_init(&s_seq, expected, count);
s_cb = cb;
s_run = true;