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 resultstype— media type (IMAGE)albumIds— array of album UUIDsisFavorite— boolean filtertakenAfter— ISO 8601 datetime stringtakenBefore— 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 8601bool hasValidTime()— returns false if year < 2020 (RTC never synced)
Files Modified
src/immich_client.h— addRandomFetchFilterstruct +fetchOneRandomAssetId()declarationsrc/immich_client.cpp— implementfetchOneRandomAssetId()src/settings.h— addtimer_last_datefieldsrc/settings.cpp— add NVS read/write fortimer_datesrc/main.cpp— refactortimerWakeCycle(), seed system clock from RTC at bootsrc/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
PhotoQueuewithfetchRandomAssets(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=cursorreturns empty, reset cursor to "" (wraps around) - Album has no favorites:
isFavorite=true+ albumIds returns empty, fallback dropsisFavorite