Skip to content

fix(esp): route daily noon capture through the restart cold-start - #150

Merged
cofade merged 1 commit into
mainfrom
fix/esp-noon-capture-restart-path
Jun 6, 2026
Merged

fix(esp): route daily noon capture through the restart cold-start#150
cofade merged 1 commit into
mainfrom
fix/esp-noon-capture-restart-path

Conversation

@cofade

@cofade cofade commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Issue #143: a field module uploaded near-black images at its daily noon capture, while a power-cycle restart always produced a good image. This routes the noon capture through the same proven-good camera cold-start the boot/restart path uses.

This is a mitigation, not a confirmed fix — #143 stays open (see "Honesty" below).

What the bench investigation found (and ruled out)

Reproduced both capture paths on a healthy bench module (hive-02, COM9) against a local stack and compared cold vs warm frames directly:

variable tested idle cold-grab result
missing warm-up (UXGA / PSRAM path) 20–160s exposes fine — identical to warm
missing warm-up + forced VGA/internal-DRAM (the field config) 20–40s exposes fine — identical to warm

So the issue's leading hypothesis ("noon skips the warm-up") is disproven on healthy hardware — its candidate fix would have changed nothing. The affected board's "PSRAM not found" is itself a hardware-fault symptom (with reset_reason=7 watchdog + flash read err, and variable black severity), so the root cause is most likely that board's marginal hardware, which a healthy bench board cannot emulate by flipping a code path. Full writeup: the chapter-11 lesson in this PR + the issue comment.

The change

  • primeCameraLikeBoot() (ESP32-CAM/ESP32-CAM.ino): PWDN power-cycle + reinit + 3-frame warm-up before the noon grab — the restart cold-start.
  • New non-aborting recoverCameraSoft() (esp_init.cpp): the noon re-prime must never abort() from steady-state — on a marginal board that would risk a daily panic → reboot → (after 3) firmware rollback. The boot path keeps initEspCamera()'s abort() (load-bearing for OTA rollback), gated by a new abortOnFailure param (default true, so every existing caller is unchanged).
  • Fail-safe: a reinit failure or an all-NULL warm-up skips that day's capture and retries next loop; lastCaptureDay is set only when prime and upload both succeed (mirrors the first-capture-on-boot gate).

Honesty: this is an unvalidated mitigation

The bench could not reproduce the failure on healthy hardware, so the fix could not be confirmed. The code comments and docs say so explicitly. It routes the daily image through the one path observed to always work, which is robust regardless of the (likely hardware) mechanism — but it intentionally leaves #143 open until the on-b0696 test settles the root cause.

Out of scope / follow-up

Testing

Done:

  • pio run -e esp32cam builds clean.
  • Native/unit suites unaffected (firmware-only change; no lib/ helper touched).
  • Bench-flashed a real module (longhorn + a throwaway diagnostic) and confirmed the boot / cold / warm capture paths upload to a local stack; compared frames directly (the table above).
  • Three rounds of senior-reviewer; the non-aborting design + warm-up-NULL gate are the result.

Could NOT do — needs the affected board (b0696ef23a08, or any "PSRAM not found" board) on USB:

  • Validate the fix against the actual failure. Plan:
    1. $env:DEV_SERVER_HOST="192.168.178.49" (your dev-box LAN IP); pio run -d ESP32-CAM -e esp32cam -t upload --upload-port COM9 — or flash via the web installer.
    2. python scripts/esp_capture.py COM9 90 — confirm -- priming camera (restart-equivalent cold-start), the loop:primeCamera:noon breadcrumb, and non-NULL prime warm-up frame N OK (… bytes) lines.
    3. Trigger a real noon capture (or temporarily change tm_hour == 12 to the current hour, or set the device clock) and confirm the uploaded noon frame is well-exposed, not near-black.
    4. Regression: confirm a healthy module still captures normally at boot and noon.

🤖 Generated with Claude Code

@cofade
cofade marked this pull request as ready for review June 6, 2026 22:30
The scheduled noon capture was a bare single esp_camera_fb_get() after
~8h of sensor idle; a field module uploaded near-black noon frames while
a restart always produced a good image (see #143). loop()'s noon branch
now calls primeCameraLikeBoot() — a PWDN power-cycle + reinit + 3-frame
warm-up, the same cold-start the boot path runs — before the grab.

The reinit goes through a new non-aborting recoverCameraSoft() (the boot
path keeps initEspCamera()'s abort(), load-bearing for OTA rollback): a
reinit failure or an all-NULL warm-up skips that day's capture and
retries next loop rather than panicking a marginal board or committing
the day on a dead sensor. lastCaptureDay is set only when prime AND
upload both succeed, mirroring the first-capture-on-boot gate.

A bench A/B on healthy hardware could not reproduce the black frame
(neither the missing warm-up nor the VGA/DRAM path), so this is an
explicitly unvalidated mitigation — see the chapter-11 lesson and #143.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cofade
cofade force-pushed the fix/esp-noon-capture-restart-path branch from c527c0d to 97e46e2 Compare June 6, 2026 22:35
@cofade
cofade merged commit 6083a40 into main Jun 6, 2026
9 checks passed
@cofade
cofade deleted the fix/esp-noon-capture-restart-path branch June 6, 2026 22:38
schutera pushed a commit that referenced this pull request Jun 7, 2026
…for #150 noon-capture re-prime

PR #150 (noon-capture camera re-prime, #143) merged the firmware source
but left VERSION=carpenter / SEQUENCE=4 — identical to the deployed
release, so field modules on carpenter/seq4 would never pull it and a
rebuilt seq4 binary would silently drift (see #132). Cut a real release:
woolcarder / sequence 5. Built with build.sh (production URLs, geo key),
republished homepage/public + dist firmware.{bin,app.bin,json}.
app_md5=9d3742d7b3c76ecc62d35a6a6f1bfc72 app_size=1156640.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cofade pushed a commit that referenced this pull request Sep 1, 2026
- correct the TLS call-site count: geolocation handshake runs only on NVS
  geo cache miss (~1 in 14 boots, kGeoCacheMaxBoots), so setup() does TLS
  up to four times, not 'at least four'
- drop the false 'appears nowhere in this repo' claim (the sentence itself
  and an unrelated 8192 chunk size exist); say 'no repo file configures
  or references it'
- merge the obsolete-PIO-core entry into the existing PlatformIO section
  and reattach the orphaned explicit-interpreter tail
- the Makefile/CI do not 'always write python -m platformio' - reword to
  the true claim (a bare pio resolves to whatever pio.exe is on PATH)
- chapter 11: link the fix to the SEQUENCE-bumped release requirement
  (#150/#132 silent no-ops) and spell out that free heap != free stack
- troubleshooting: note the addr2line addresses are dump-specific
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant