Commit Graph

32 Commits

Author SHA1 Message Date
6810f2c7c6 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-05 20:28:00 -04:00
73b8dde686 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-04 20:57:59 -04:00
74e063d958 docs: add design spec for deep sleep single-fetch with probabilistic weighting
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 20:52:48 -04:00
0d1cd3392d 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-03 22:46:17 -04:00
7d260a452a 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-03 21:45:17 -04:00
a0f1b9917e 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-03 21:14:19 -04:00
8cab0c063e 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-03 21:06:48 -04:00
ccb71cbbc0 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-03 21:03:46 -04:00
db74689ced 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-03 20:50:01 -04:00
e387976220 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-03 20:22:06 -04:00
0d1349e8eb 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-03 19:54:59 -04:00
9e3092e4f4 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-03 19:52:22 -04:00
e9909c6dbf 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-03 15:53:06 -04:00
4ae0e61610 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-03 14:39:55 -04:00
630e924485 fix: suppress all e-ink refreshes during boot with setAutoDisplay(false)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 11:31:36 -04:00
959cdbc6dd 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-03 11:25:41 -04:00
7244449e0d fix: LittleFS partition label mismatch and display rotation (180°)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 11:12:34 -04:00
2192617a4d fix: add reboot endpoint and test documentation
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 11:01:04 -04:00
ecb4624815 feat: full integration with FreeRTOS tasks and slideshow state machine
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 11:00:02 -04:00
8efd1917f7 feat: web server with REST API, captive portal, and management UI
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:59:26 -04:00
8c5c39dbe3 feat: display manager for e-ink framebuffer output and text messages
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:56:55 -04:00
aad07e5813 feat: image pipeline with JPEG decode, resize, and Floyd-Steinberg dithering
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:56:12 -04:00
9bb733b9a5 feat: photo queue manager with cycling modes and favorites weighting
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:54:41 -04:00
14e67a1793 feat: Immich API client with album, asset, and download support
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:53:27 -04:00
18d1da8a80 feat: button handler with press detection and combo support
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:51:29 -04:00
c975db5b12 feat: power manager with battery monitoring, LED warnings, and sleep modes
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:50:32 -04:00
f5b651c37a feat: WiFi manager with station/AP mode and mDNS
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:49:58 -04:00
2a1db82c33 feat: settings manager with NVS persistence
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:49:13 -04:00
6af7fca1a0 chore: add .gitignore for .pio and .superpowers
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:47:43 -04:00
e55649989e feat: project scaffolding with PlatformIO config and boot skeleton
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:46:23 -04:00
214fa3bf90 Add implementation plan for Immich photo frame firmware
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:37:51 -04:00
ac5ab960b1 Add design spec: Immich photo frame for M5Stack PaperColor
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:27:08 -04:00