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

114 lines
4.8 KiB
Markdown
Raw Normal View History

# 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`