Skip to content

Add Google Photos picker, viewer, and slideshow with LVGL frontend - #136

Merged
bdbarnett merged 3 commits into
mainfrom
claude/micropython-google-photos-7i0qgp
Sep 23, 2026
Merged

bdbarnett merged 3 commits into
mainfrom
claude/micropython-google-photos-7i0qgp

Conversation

@bdbarnett

@bdbarnett bdbarnett commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

This PR adds a complete Google Photos integration for PyDevices displays, enabling users to pick photos from their phone and browse them on a microcontroller or desktop display.

Summary

Implements a Google Photos Picker API client with an LVGL-based frontend that supports photo selection via QR code pairing, thumbnail browsing, full-screen viewing, and automatic slideshows. Includes offline simulator mode for testing without credentials.

Key Changes

Core Engine (gphotos_engine.py)

  • Google Photos Picker API client with OAuth token refresh flow
  • Session management (create, poll, delete picker sessions)
  • Thumbnail caching with bounded storage on flash
  • Preferences persistence (session state, picked items, slideshow settings)
  • Portable HTTP transport layer supporting urllib, requests, adafruit_requests, and raw socket+TLS
  • JPEG/PNG size detection and image descriptor generation for LVGL

LVGL Frontend (gphotos_lvgl.py)

  • Three-page UI: connect (QR code pairing), list (thumbnail grid), view (full-screen with slideshow)
  • Cooperative background job queue drained on main LVGL timer (no threading for MCU compatibility)
  • Mailbox-based communication between network jobs and UI updates
  • Responsive layout with charcoal/teal color scheme
  • Automatic image prefetching and slideshow with configurable intervals
  • Support for JPEG (TJPGD/jpegio) and PNG (LODEPNG) decoding

Simulator (gphotos_sim.py)

  • Duck-typed engine for offline/PyScript demos
  • Auto-detects when to use simulator (no credentials, no network, or explicit GPHOTOS_SIM=1)
  • Procedurally generated sample photos with realistic metadata

Authentication Tool (tools/gphotos_auth.py)

  • One-time browser-based OAuth login for desktop (CPython stdlib only)
  • PKCE flow with loopback redirect for installed apps
  • Generates refresh token for device use

Testing & Documentation

  • Comprehensive unit tests with mocked HTTP transport
  • README with setup instructions and Google Cloud configuration
  • Asset generation script for simulator sample images
  • Gallery and PyScript integration

Implementation Details

  • No threading on MCU: All network I/O queued and processed on LVGL's main timer to avoid stack overflow on ESP32
  • Portable HTTP: Graceful fallback chain from urllib → requests → adafruit_requests → raw socket
  • Image handling: Extracts dimensions from JPEG/PNG headers; registers LVGL decoders for both formats
  • Token lifecycle: Automatic refresh with 60-second margin before expiry; URL cache invalidation at 50-minute mark
  • Offline-first: Simulator runs by default when no credentials exist, enabling gallery/PyScript demos

Google Photos on a PyDevices display. Since Google's March 2025 Library API
cutoff, third-party apps can only show photos the user explicitly picks, so
the example drives the Photos Picker API: the board shows a picker session
as a QR code, the user picks on their phone, and the board lists the picks
with thumbnails, views them full screen, and runs a slideshow.

- lib/examples/google_photos/gphotos_engine.py: UI-agnostic Picker API
  client (refresh-token auth, sessions, paged listing, sized thumbnail
  downloads with a bounded flash cache, prefs) over urllib / MicroPython
  requests / adafruit_requests / raw socket + TLS.
- gphotos_sim.py: simulator with sample photos for the gallery, PyScript,
  offline, or no-credentials runs (same shape as roku_sim).
- gphotos_lvgl.py: LVGL front end with connect (QR), paged list, viewer,
  slideshow; network work drained from an lv.timer pump, no _thread. JPEG
  via TJPGD on CPython or displayif's jpegio LVGL decoder on MCU builds.
- google_photos.py launcher mirrors roku_remote, and keeps -m launches
  alive (the host loop misreads package -m launches as a REPL).
- tools/gphotos_auth.py: one-time PC-side Google sign-in (stdlib only,
  PKCE, loopback redirect) that writes the tokens file to copy to a board.
- tests/test_gphotos_engine.py: 27 tests with faked HTTP for the engine,
  transports, simulator, and login tool.
- README with Google Cloud setup, limits, and per-platform decoder notes;
  screenshots; MIP/TOML manifests, gallery card, and test-kit entries.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTpFxNSKF3xWnX6xhgPS7T
Bring the branch up to date with main (audiolive example, the
audioif -> audiodsp rename, and the new gallery-payload staging test).
Regenerated .site/gallery, .site/pyscript, packages/examples.json, and
pydevices-examples.toml with the repo's own generators rather than
relying on git's line merge, and confirmed both match --check exactly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTpFxNSKF3xWnX6xhgPS7T
CI's new test_every_module_a_card_imports_is_copied (from the main
merge) caught a real gallery bug: google_photos.py's `# modules:`
header put gphotos_engine/gphotos_sim/gphotos_lvgl into the card's
bare `?modules=` query, but those files are staged under the
google_photos/ package prefix via `manifests=google_photos`, not at
the tree root -- a browser loading that card would 404 on them.

`# modules:` is for a module-kind card's extra flat sibling files
(e.g. calc_lvgl.py pulling in the top-level calc_engine.py); a
manifest-kind package entry never needs it for its own package-internal
siblings, matching the existing roku_remote.py precedent. Removing the
line lets discover_package_py_files stage the package as-is and the
card link becomes plain `?manifests=google_photos`.

Regenerated the gallery pages and MIP manifests with the repo's
generators; full unittest suite, ruff, and `gallery_generator.py
--check` all pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTpFxNSKF3xWnX6xhgPS7T
@bdbarnett
bdbarnett merged commit 3e84c7b into main Sep 23, 2026
4 checks passed
@bdbarnett
bdbarnett deleted the claude/micropython-google-photos-7i0qgp branch September 23, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants