This repo is a showcase: examples and a PyScript gallery for the PyDevices
driver stack, not the product itself. The shareable product libraries and their
docs live in sibling pydevices. There is no build step for examples.
.site/pyscript/lib is a symlink to ../../lib, so editing lib/ updates the
PyScript gallery too.
Docs map (this repo's docs/ is small on purpose — see
docs/README.md): docs/peterhinch-guis.md
(Nano-GUI / Micro-GUI / MicroPython-Touch), docs/tft-gui.md
(@russhughes TFT GUI stub), docs/screenshots/.
Code-adjacent runbooks: tools/README.md (example test
matrix, PyScript/Playwright debugging, LVGL timer harnesses),
lib/utils/README.md, scripts/README.md,
tests/README.md.
displaydev, audiodev, optional appdev, multimer, events, keys,
and portable hardware utilities live in sibling
pydevices, which
also owns TestPyPI/MIP publishing. Non-LVGL examples instantiate
appdev.App(board_config); LVGL examples import it from display_driver.
Board configs never own an appdev.App. AutoDisplay is imported from
displaydev.auto only.
- Cursor Cloud (multi-repo workspace): do not use a local
.cursor/environment.jsonin this repo. The canonical cloud environment lives in PyDevices/.github — start Cloud Agents from that repo (orcmods) with the saved Pydevices Cloud Workspace environment. Its install command isbash scripts/cloud-workspace-install.sh(relative to the.githubcheckout), which symlinks/agent/repos/*into~/gh/pydevices/. See AGENTS.md there. - Use the repo-root virtualenv at
.venvfor all Python tooling (.venv/bin/python,.venv/bin/ruff,.venv/bin/pytest). The systempython3has no project dependencies installed. - Browser testing with Playwright: see
.agents/rules/playwright_environment_rule.md. - Desktop matrices use repo
.venv(cpython-venv) plus interpreters onPATH(micropython,circuitpython, and when presentmicropython.exe/python.exe).jupyter.py(frompydevices/bin),./scripts/pyscript.sh, andandroid.py(frompydevices/bin, onPATH) aid Jupyter, PyScript, and Android (adb stage ontoorg.pydevices.runner; cwd paths like CLI Python — not PyScript gallery). Opt-in matrix:tools/example_test_kit.py --only-interpreter android …. - The desktop display backend on CPython on Windows is
PGDisplay(pygame-ce;import pygame). Preferpython.exefor PG work. Do not install pygame-ce into.venv/ systempython3on this laptop — those stay SDL-primary;board_configfalls back toSDLDisplaywhen pygame-ce's publicpygame.WindowAPI is missing.pygame-ceis intentionally not inrequirements-dev.txt.
- Unit tests (stdlib
unittest, no third-party runner needed):.venv/bin/python -m unittest discover -s tests - Lint/format:
.venv/bin/ruff check --force-exclude --extend-exclude '*.ipynb' lib tests toolsand.venv/bin/ruff format --check --force-exclude --extend-exclude '*.ipynb' lib tests tools. Notepyproject.tomlexcludeslib/examples/**(and a few others) from ruff, so example files are not linted/formatted; do not be surprised whenruff format --checkon an example path reports a diff. - The pre-commit hooks (
.pre-commit-config.yaml) areruff-check,ruff-format(python/pyi only), andnbstripoutfor notebooks.ruffdoes not lint*.ipynbunder the hook config, so pre-existing notebook findings fromruff checkon the whole tree can be ignored.
-
Read
tools/README.md— Example test matrix first — agent runbook for the cross-interpreter example test system. Canonical interpreter list:tools/example_interpreters.toml; per-example metadata:tools/example_test_manifest.toml. -
Preferred thorough gate: example-by-example with all selected interpreters in parallel (
--jobs 0): 5 desktop for sync, 7 for async; bothPYDEVICES_TIMER_ASYNC=0and=1,--fail-fast, line-buffered live log, fix after a failed example wave then resume — see Preferred method and Windows PE under WSL. Do not forwardSDL_*to*.exe(PE windows should appear; unix stays headless from the shell export). A PEhangwith a live window means quit failed, not that PE failed to start.--curated-onlyis smoke only. -
--only-example/--only-interpretertake space-separated ids on one flag (--only-interpreter circuitpython python.exe). Repeating the flag keeps only the last list — see tools/README.md. -
Quick headless CPython smoke:
SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \ .venv/bin/python tools/example_test_kit.py --no-unit-tests --only-interpreter cpython-venv
-
PyScript hangs / CDP: prefer Playwright helpers and tools/README.md — PyScript headless debug before poking the IDE browser.
Host defaults and env semantics:
App and board config — timer_async.
Examples never read this variable — only library board_config and harnesses
that call displaydev.env_set.
Preferred for agents / matrix: pass wrapper --timer-async (the example
kit does this). That uses env_set and works for Windows PE under WSL without
relying on OS environ. Shell export remains a valid host shortcut:
PYDEVICES_TIMER_ASYNC=1 SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy \
.venv/bin/python tools/example_test_kit.py --no-unit-tests --only-interpreter cpython-venvlv_test_timer.py follows app.timer_async and does not set env vars.
To force async on desktop for that example (or the LVGL kit), set
PYDEVICES_TIMER_ASYNC=1 on the parent process before launch, or use a kit that
passes --timer-async.
micropython.exe matrix: no threading / _thread. See
tools/README.md — Interpreters and binaries.
- Non-LVGL examples instantiate
appdev.App(board_config). LVGL's frozen/bundleddisplay_driverowns an independent coordinator and does not importappdev. Both consume neutral board-config callables and usemultimer; display drivers remain policy-free.
Full guidance:
pydevices — Background work on MicroPython (_thread).
App pattern: queue work and drain on the main tick — see roku_widgets /
roku_lvgl / roku_graphics (_run_bg + _drain_bg). Do not “fix” this in
appdev with speculative reentrancy guards — keep the pattern in the app.
- Install the CPython LVGL binding from TestPyPI (import name
lvgl):.venv/bin/pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ pydevices-lvgl(see https://github.com/PyDevices/lvgl-python). The update script installs it. display_driver(frozen in MP/CP LVGL firmwares; bundled withpydevices-lvgl) owns the LVGLevent_loop(tick viaapp.on_tick,asynciofrommultimer) and claims app display refresh so LVGL presents frames fromtask_handler. SoT: lvgl-bindingspython/display_driver.py— not shipped from this repo.- Test LVGL timers with
tools/lv_timer_test_kit.py(modes:sync,async). Headless:SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy .venv/bin/python tools/lv_timer_test_kit.py --only cpython-venv. - Non-obvious: the sync
multimer.auto.Timerprovider on CPython/Linux delivers via a main-thread signal handler. LVGL is not re-entrant, so the app loop must not touch LVGL/pygame concurrently while that tick runs; LVGL examples use cooperative deadline/time.sleep(sync) orasyncio.sleep(async). The LVGL timer kit covers dedicated click checks — its daemon-thread quit injection is incompatible with the generic example matrix for some ports. multimeris fragile — before editing hardwarelib/multimer/, read multimer concepts and follow the local Cursor rulemultimer-fragile(thinking model, small diffs, revert failures). Do not duplicate that rule text in this repo.
When bringing up or debugging a MicroPython board_configs/fbdisplay/* board
that uses displayif (mipidsi, rgbframebuffer, picodvi, …), especially with
LVGL and mpftp soft-reset:
- Soft-reset + re-import is the acceptance test (no hard reset).
- Prefer
mip.installover Wi‑Fi for large Python trees; mpftp for thin files and firmware. - Symptom table, wrap architecture, and bring-up methods live in the sibling
displayif repo:
soft-reset-and-bring-up.md(local:../displayif/docs/soft-reset-and-bring-up.mdor~/gh/pydevices/displayif/…). Start at displayifAGENTS.md. - Do not leave flash-backed debug logs on the touch/refresh path (looks like flicker). Fix displayif/bindings root causes rather than board_config workarounds.