Commit Graph

32 Commits

Author SHA1 Message Date
531906ca2c 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-06 01:39:17 -04:00
d95a6db79e 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-05 20:57:53 -04:00
b2f85d752a 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-05 20:42:25 -04:00
ac6c6eaf79 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
b9e5044fd2 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
3483b9bef8 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
033ab8d33b 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
d41ba0bd8a 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
d54e468c73 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
6b8d332107 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
758fd4e123 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
5aabfe0e9b 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
cda56142a3 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
51756be080 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
fe20841d9e 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
392061bfa0 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
127205de2e 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
26e30620c4 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
afcf4cb81e fix: LittleFS partition label mismatch and display rotation (180°)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 11:12:34 -04:00
b16783294e fix: add reboot endpoint and test documentation
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 11:01:04 -04:00
034a061293 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
5a91cd1590 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
1610a4d94c 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
7aaf920658 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
9166bfe0ce 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
8f85015339 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
841169b9b9 feat: button handler with press detection and combo support
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:51:29 -04:00
7f33d06fb0 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
442303c485 feat: WiFi manager with station/AP mode and mDNS
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:49:58 -04:00
881a20f911 feat: settings manager with NVS persistence
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:49:13 -04:00
9ba3e89aae chore: add .gitignore for build artifacts
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:47:43 -04:00
a24c3de0a8 feat: project scaffolding with PlatformIO config and boot skeleton
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:46:23 -04:00