Files
immich-frame/docs/superpowers/specs/2026-08-04-deep-sleep-single-fetch-design.md

4.8 KiB

Deep Sleep Single-Fetch with Probabilistic Weighting

Date: 2026-08-04 Status: Approved

Overview

Refactor the deep sleep wake path (timerWakeCycle) to fetch a single random photo per wake using probabilistic server-side filtering based on cycle_mode. Replaces the current 10-asset batch fetch with a leaner single-call approach that honors weighting settings while minimizing API calls and battery consumption.

Motivation

In deep sleep slideshow mode, buttons don't respond and there's no photo queue. The device simply wakes, shows one photo, and sleeps again. Fetching 10 assets and picking one wastes HTTP round-trips, bandwidth, and battery. A single POST /api/search/random call with size=1 and appropriate filters is sufficient.

Immich API Capabilities

The POST /api/search/random endpoint (Immich v3) supports these filters relevant to our use case:

  • size — number of results
  • type — media type (IMAGE)
  • albumIds — array of album UUIDs
  • isFavorite — boolean filter
  • takenAfter — ISO 8601 datetime string
  • takenBefore — ISO 8601 datetime string

These server-side filters allow meaningful weighting without fetching large batches.

Design

Filter Selection per Cycle Mode

Mode Filter applied Probability Fallback
Random No extra filter 100% —
Chronological takenAfter=last_shown_date 100% Reset date to epoch
ReverseChronological takenBefore=last_shown_date 100% Reset date to "now"
FavoritesWeighted isFavorite=true 66% Drop isFavorite
FavoritesWeighted No extra filter 33% —
WeightedChronological takenAfter=(now - 30 days) 66% Drop date filter
WeightedChronological No extra filter 33% —
WeightedReverseChronological takenBefore=(now - 30 days) 66% Drop date filter
WeightedReverseChronological No extra filter 33% —

Album IDs from settings are always included alongside any other filter.

Retry Logic

attempt = 0
while (!displayed && attempt < MAX_RETRIES):
  1. Choose filter based on cycle_mode + probability roll
  2. POST /api/search/random (size=1, filter + albumIds)
  3. If empty response:
     - If using a filter: retry without filter (relax)
     - If already unfiltered: give up (no photos available)
  4. Download + process the asset
  5. If download/process fails: increment attempt, loop
  6. If success: display, update timer_last_date in NVS, break

MAX_RETRIES = 5 (matches current TIMER_WAKE_RETRIES).

NVS Persistence

New Settings field: timer_last_date (String, ISO datetime).

  • Updated after each successful display in deep sleep mode
  • Used as the cursor for Chronological and ReverseChronological modes
  • Empty string means "no cursor" (start from beginning/end)

RTC Initialization (Firmware-Wide)

The PaperColor has a hardware RX8130CE Real-Time Clock (I2C 0x32) that persists across deep sleep. It is not currently used in the firmware.

At boot (both paths), before WiFi:

  • Seed ESP32 system clock from hardware RTC via M5.Rtc.getDateTime() + settimeofday()

After WiFi connects (both paths):

  • Sync system clock from NTP via configTime(0, 0, "pool.ntp.org")
  • Write corrected time back to hardware RTC via M5.Rtc.setDateTime()

Benefits:

  • Deep sleep date filters are reliable
  • Normal mode metadata overlays (strftime) display correct time
  • Future time-dependent features become trivial

Date Helpers

New src/time_utils.h:

  • String isoNow() — current time as ISO 8601 (e.g., "2026-08-04T17:30:00.000Z")
  • String isoNowMinus30d() — 30 days ago as ISO 8601
  • bool hasValidTime() — returns false if year < 2020 (RTC never synced)

Files Modified

  • src/immich_client.h — add RandomFetchFilter struct + fetchOneRandomAssetId() declaration
  • src/immich_client.cpp — implement fetchOneRandomAssetId()
  • src/settings.h — add timer_last_date field
  • src/settings.cpp — add NVS read/write for timer_date
  • src/main.cpp — refactor timerWakeCycle(), seed system clock from RTC at boot
  • src/wifi_manager.cpp — add NTP sync after WiFi connect, write back to hardware RTC
  • New: src/time_utils.h — ISO date helpers

Normal Mode Impact

  • Slideshow logic: Untouched. Normal mode still uses PhotoQueue with fetchRandomAssets(50).
  • RTC/NTP sync: Applied to both modes as a general firmware improvement.

Edge Cases

  • No photos match filter: fallback to unfiltered, then give up
  • RTC never synced (first boot or battery drained): detect year < 2020, fall back to random for weighted modes
  • Chronological cursor exhausted: when takenAfter=cursor returns empty, reset cursor to "" (wraps around)
  • Album has no favorites: isFavorite=true + albumIds returns empty, fallback drops isFavorite