Commit Graph

34 Commits

Author SHA1 Message Date
84cdc9f877 chore: add internal docs directory to .gitignore
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:42 -04:00
0929148f5d feat: add deep sleep batch caching, wake log, and log viewer tools
- Batch-fetch asset IDs in deep sleep mode with NVS caching to reduce
  network calls and improve battery life (configurable batch size 1-50)
- Add wake_log module: persistent CSV log of each photo cycle surviving
  reboots and deep sleep, with download/clear via web UI
- Add browser-based log viewer and log comparison tools (tools/)
- Update .gitignore to exclude .cursor/, chat-summaries/, wake_*.csv,
  and .DS_Store

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:42 -04:00
21ab983522 fix: call configTime() in minimal WiFi path to initialize DNS resolver
Without configTime(), the ESP32 DNS subsystem isn't properly
initialized after WiFi connect, causing HTTPS hostname resolution
to fail silently in the deep sleep wake path. The fix calls
configTime() fire-and-forget (non-blocking, <1ms) which initializes
DNS without the 5-second NTP wait.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:42 -04:00
1d89b1663f feat: add Sync Time button to Device page in web UI
Adds a 'Sync Time' button in the Device Actions section that calls
POST /api/action/sync-time to trigger NTP sync and RTC write.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:42 -04:00
f59143d98f feat: add POST /api/action/sync-time endpoint for manual NTP/RTC sync
Allows triggering NTP sync + hardware RTC write from the web UI,
useful for periodic manual time correction (e.g., every 30 days
or when charging).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:42 -04:00
e2f538505d fix: skip NTP sync and mDNS in deep sleep wake path to reduce battery drain
The syncNTP() call blocks up to 5 seconds waiting for NTP response on
every deep sleep wake cycle (every 10 minutes). Combined with mDNS
startup, this added significant active WiFi time per cycle.

Fix: Add 'minimal' flag to WiFiManager::begin()/startStation(). In
the deep sleep wake path, skip NTP sync and mDNS entirely. The system
clock is already seeded from the hardware RTC at boot, which is
accurate enough for 30-day date window calculations.

Normal interactive boot still does full NTP sync + mDNS as before.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:42 -04:00
44f1634d71 feat: deep sleep single-fetch with probabilistic weighting
Refactor timerWakeCycle() to fetch a single random photo per wake
using cycle-mode-aware server-side filtering via Immich's
POST /api/search/random endpoint.

Changes:
- Add fetchOneRandomAssetId() to ImmichClient with support for
  isFavorite, takenAfter, takenBefore filter parameters
- Add timer_last_date NVS field for chronological cursor persistence
- Implement probabilistic filter selection per cycle mode:
  - Random: unfiltered size=1
  - Chronological/ReverseChronological: date cursor in NVS
  - FavoritesWeighted: 66% favorites, 33% unfiltered
  - WeightedChronological: 66% recent (30d), 33% unfiltered
  - WeightedReverseChronological: 66% older (30d), 33% unfiltered
- Add retry loop with filter relaxation on empty results
- Initialize hardware RTC (RX8130CE) firmware-wide:
  - Seed system clock from RTC at boot (immediate time)
  - NTP sync after WiFi connect, write back to RTC
- Add time_utils.h with isoNow(), isoNowMinus30d(), hasValidTime()

Normal mode (PhotoQueue + displayTask) is completely untouched.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:42 -04:00
90f208c31c feat: wire up album filtering to photo queue
Album selection now controls which photos are displayed.
Uses POST /api/search/random with albumIds filter (Immich v3
compatible) instead of the removed GET /api/albums/{id} assets.

- Add albumIds parameter to fetchRandomAssets/fetchRandomAssetIds
- syncFromAlbums: fetch random assets filtered by selected albums
- syncFromAllPhotos: preserve existing global random behavior
- Album mode reshuffles on exhaustion instead of re-fetching
- 24h resync checks album assetCounts for changes before re-fetch
- requestSync + albums_changed action triggers queue rebuild on save
- timerWakeCycle respects album selection
- Discard stale pre-renders on album change

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:41 -04:00
1bda235936 feat: pre-render next photo for instant slideshow transitions
Refactor displayTask() into two phases: display and pre-render.
After showing a photo, the next one is immediately downloaded,
decoded, and dithered into a pending framebuffer (~240KB PSRAM).
When the slideshow timer fires, the EPD refresh starts instantly
without the previous 2-5s pipeline delay.

- Add PendingPhoto struct with settings hash for staleness guard
- Extract displayPhoto() and prepareNextPhoto() helpers
- Manual next/random discards pre-render and goes inline
- Falls back to inline pipeline if pre-render unavailable
- No changes to ImagePipeline, DisplayManager, or other modules

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:41 -04:00
aa6db8884f feat: add 2m, 10m presets and custom duration for slideshow interval
Presets are now [1, 2, 5, 10, 15, 30, 60] minutes. A 'Custom' button
reveals a number input clamped to 1–60 minutes. Backend also clamps
the received value with constrain(v, 1, 60) for safety.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:41 -04:00
2877de6027 fix: improve icon vertical alignment and increase icon-text gap
Shift icons up 1px to better match the text midline, and increase
ICON_GAP from 4 to 6 for more breathing room between icon and text.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:41 -04:00
8637db2122 feat: add procedural pin and calendar icons to metadata badges
Draw pin icon on the location badge and calendar icon on the
datetime badge when displaying a single photo (not side-by-side).
Icons are rendered using M5GFX shape primitives (fillCircle,
fillTriangle, drawRect, fillRect, drawLine) - no bitmap data needed.

Icons only appear when regionW == DISPLAY_WIDTH to avoid cluttering
the narrower badges used for portrait-pair layouts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:41 -04:00
02625c7c58 feat: metadata overlay tweaks — location badge, rounded corners, translucent option
- Location gets its own badge in the lower-left corner (separate from
  the main date/time/people/camera badge at the configured position)
- All overlay badges now use rounded corners (radius 4px) via
  fillRoundRect instead of fillRect
- New setting: 'Translucent background?' checkbox in web UI
  When enabled, badge backgrounds use a checkerboard pattern (every
  other pixel black) instead of solid black, creating a 50% opacity
  effect that lets the underlying image show through
- Refactored all badge drawing through a single drawOverlayBadge()
  helper (metadata, sleep indicator, battery indicator)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:41 -04:00
139cd84a0a feat: replace JPEGDEC with stb_image for robust JPEG decoding
JPEGDEC (error 3: UNSUPPORTED_FEATURE) and M5GFX's TJpgDec both failed
on ~70% of Immich preview images due to:
- Optimized Huffman tables exceeding 12-bit DC code limit
- Extended sequential mode (SOF1) misidentified as baseline
- Progressive JPEG not handled by either decoder

stb_image.h handles all standard JPEG modes (baseline, progressive,
extended sequential, arbitrary Huffman tables) in a single decoder.

Changes:
- Add vendored stb_image.h (JPEG-only, PSRAM allocator)
- Rewrite decodeAndFit() to use stbi_load_from_memory()
- Remove JPEGDEC library dependency
- Remove jpegDrawCallback, DecodeContext, parseJpegHeader,
  decodeFallbackLGFX (all obsolete)
- Flash usage decreased: 1345KB -> 1317KB

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:41 -04:00
5518fbd5d3 refactor: use EPD-exact bgr888 palette with bulk pushImage
Replace the PALETTE_565 + writePixel loop with a bgr888_t palette whose
values exactly match Panel_ED2208's internal epd_palette, pushed via a
single pushImage() call.

Benefits:
- Eliminates ~240K individual writePixel calls per frame
- Zero-distance palette values guarantee correct color mapping
- Removes lossy RGB565 intermediate encoding
- Remove unused paletteToColor565() helper

Also adds informational comment to triggerRefresh() noting that
Panel_ED2208::_exec_transfer always sends the full 400x600 frame.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:41 -04:00
f3df685d02 fix: eliminate diagonal dither pattern from display driver
Root cause: Panel_ED2208's epd_quality mode applies _dither_row_rgb_pair
(diagonal bias pattern with dither=140) during _exec_transfer(). Since our
ImagePipeline already does proper Floyd-Steinberg dithering, the driver's
additional dithering was creating visible diagonal artifacts.

Fix: setEpdMode(epd_fastest) selects _dither_row_none (clean nearest-color
lookup, no spatial bias). On Panel_ED2208 this only affects the dither
function — NOT refresh quality or waveform.

Also: default dither_noise setting to OFF since error scatter was targeting
the wrong layer (it made gradient areas fuzzier without fixing the real
problem in the display driver).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:41 -04:00
c2cab25624 feat: blurred letterbox, portrait pairs, overlay metadata, button fixes
- Letterbox: extend edge pixels outward with blur + fade to solid color
- Portrait: detect and pair portraits side-by-side, fallback to solo
- Metadata: add overlay mode (corner badge), time field, custom date/time
  format strings (strftime), font size 1.5x, region-aware for pairs
- Buttons: swap G9/G10 to match physical layout, double-press random
  button for deep sleep, SLP indicator drawn before sleep
- Boot: remove redundant 'Loading photos' screen (saves 17s refresh)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:41 -04:00
b80f6e4bde feat: fit-contain letterbox + smart queue refill
- Replace center-crop with fit-contain (no photo cropping)
- Letterbox bars filled with adjacent edge average color
- Queue fetches in batches until 50 usable assets
- Auto-refill queue on exhaustion, dedup via shown history
- Filter RAW files from asset selection

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:41 -04:00
4b9a7f2fa7 fix: suppress all e-ink refreshes during boot with setAutoDisplay(false)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:40 -04:00
90eed9d5ff fix: single boot refresh with QR code, async WiFi scan, skip light sleep in AP mode
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:40 -04:00
37670e56a2 fix: LittleFS partition label mismatch and display rotation (180°)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:40 -04:00
8fb9d75229 fix: add reboot endpoint and test documentation
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:40 -04:00
18b2518c33 feat: full integration with FreeRTOS tasks and slideshow state machine
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:40 -04:00
c98e34256e feat: web server with REST API, captive portal, and management UI
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:40 -04:00
798b0d0276 feat: display manager for e-ink framebuffer output and text messages
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:40 -04:00
c87e3efdc8 feat: image pipeline with JPEG decode, resize, and Floyd-Steinberg dithering
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:40 -04:00
458bcc5200 feat: photo queue manager with cycling modes and favorites weighting
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:40 -04:00
07371b63fa feat: Immich API client with album, asset, and download support
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:40 -04:00
076415a6c5 feat: button handler with press detection and combo support
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:39 -04:00
e2af736e0d feat: power manager with battery monitoring, LED warnings, and sleep modes
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:39 -04:00
5f6f3b90b0 feat: WiFi manager with station/AP mode and mDNS
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:39 -04:00
71ff813fdc feat: settings manager with NVS persistence
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:39 -04:00
3879ba0654 chore: add .gitignore for build artifacts
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:39 -04:00
e3ca5af020 feat: project scaffolding with PlatformIO config and boot skeleton
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-31 13:23:39 -04:00