Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/screenshots/google_photos_connect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions lib/examples/google_photos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,16 @@ token for hour-long access tokens.
overrides the path.

Then run the example, tap **PICK**, scan the code with your phone, pick
photos, tap **Done** — the list appears on the board.
photos, tap **Done** in Google Photos — the board notices by itself and the
list appears. There is nothing to press on the board.

## Using it

| Page | Buttons |
|---|---|
| **list** | **PICK** new session · **SLIDES** slideshow from the first photo · **MORE** next page (long picks are paged to keep RAM bounded) · tap a row to view it |
| **view** | **BACK** · **PREV** · **PLAY / PAUSE** slideshow · **NEXT** · tapping the photo also advances |
| **connect** | QR code of the session link · **OPEN** (desktop only: opens the link in the local browser) · **RETRY** new session · **BACK** to the list |
| **connect** | QR code of the session link, the three steps (scan, pick, tap Done), and a live "Waiting for your picks" line; the page moves on by itself when you tap Done · **Open on this PC** (desktop only: opens the link in the local browser) · **New link** fresh session · **Back** to the list you had |

The slideshow interval is `slideshow_s` in the prefs file (default 5).

Expand Down
34 changes: 29 additions & 5 deletions lib/examples/google_photos/google_photos.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,15 @@
"zephyr",
)
if getattr(sys, "platform", "") not in _MCU_PLATFORMS:
env_set("PYDEVICES_WIDTH", _WIDTH)
env_set("PYDEVICES_HEIGHT", _HEIGHT)
if _SCALE is not None:
env_set("PYDEVICES_SCALE", _SCALE)
# None leaves the environment alone, so PYDEVICES_WIDTH / _HEIGHT set by
# the caller (or tools/screenshot.py --resolution) still apply.
for _name, _value in (
("PYDEVICES_WIDTH", _WIDTH),
("PYDEVICES_HEIGHT", _HEIGHT),
("PYDEVICES_SCALE", _SCALE),
):
if _value is not None:
env_set(_name, _value)

import gphotos_engine # noqa: E402
from gphotos_sim import make_engine # noqa: E402
Expand Down Expand Up @@ -113,7 +118,7 @@ def _block_if_batch():
from appdev import _hostloop
from display_driver import app

if not _hostloop.batch() or _hostloop.ambient():
if not _launched_with_m(_hostloop) or _hostloop.ambient():
return
if getattr(getattr(sys, "flags", None), "interactive", 0):
return
Expand All @@ -134,4 +139,23 @@ def _block_if_batch():
timer.sleep_ms(50)


def _launched_with_m(_hostloop):
"""``-m`` / ``-c`` launch, even where the OS command line is unreadable.

``_hostloop.batch()`` reads the real command line. On micropython.exe that
read fails (pydevices <= 0.5.0 decodes it with a UTF-16 codec MicroPython
doesn't have), so a ``-m`` launch looked like a script run and the process
returned after ~2 s (#142). MicroPython's own tell is ``sys.argv``: ``-m
pkg`` leaves ``["pkg"]``, a script leaves its ``.py`` path, a REPL ``[]``
or ``[""]``.
"""
if _hostloop.batch():
return True
if getattr(sys.implementation, "name", "") != "micropython":
return False
argv = getattr(sys, "argv", None) or [""]
first = argv[0] or ""
return bool(first) and not first.endswith(".py") and not first.endswith(".mpy")


main()
33 changes: 28 additions & 5 deletions lib/examples/google_photos/gphotos_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -861,12 +861,35 @@ def poll_session(self):
self.last_error = self._api_error("poll session", status, data)
return False

def delete_session(self):
"""Drop the picker session (Google forgets the picks); keeps the list."""
s = self.session
self.session = None
self._urls_at = None
def snapshot(self):
"""The current session + picked list, for :meth:`put_back`.

A new session clears the list (its picks replace it). Take a snapshot
first when the user may back out of picking and want the old list.
"""
return (self.session, list(self.items), self._urls_at)

def put_back(self, snap):
"""Restore a :meth:`snapshot`; returns the session it replaced."""
replaced = self.session
self.session, items, self._urls_at = snap
self.items = list(items)
self._save_prefs()
return replaced

def delete_session(self, session=None):
"""Drop the picker session (Google forgets the picks); keeps the list.

With ``session``, delete that one at Google and leave ``self.session``
alone (an abandoned session after :meth:`put_back`).
"""
if session is not None:
s = session
else:
s = self.session
self.session = None
self._urls_at = None
self._save_prefs()
if not s or not s.get("id"):
return True
status, data = self._api("DELETE", PICKER_BASE + "/sessions/" + quote(s["id"]))
Expand Down
Loading
Loading