diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..bf70e27e0 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,36 @@ +# The docs site as Read the Docs builds it: from the repository root, with +# docs/requirements.txt, and every warning a failure. +name: Docs + +on: + push: + branches: [main] + paths: + - 'docs/**' + - 'lib/**' + - '.readthedocs.yaml' + - '.github/workflows/docs.yml' + pull_request: + paths: + - 'docs/**' + - 'lib/**' + - '.readthedocs.yaml' + - '.github/workflows/docs.yml' + workflow_dispatch: + +permissions: + contents: read + +jobs: + docs: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: '3.13' + - name: Install docs requirements + run: pip install -r docs/requirements.txt + - name: Build docs + run: sphinx-build -W --keep-going -b html docs _site diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..6a17e5fca --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,14 @@ +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.13" + +sphinx: + configuration: docs/conf.py + fail_on_warning: true + +python: + install: + - requirements: docs/requirements.txt diff --git a/AGENTS.md b/AGENTS.md index c5c5f8ccd..914052f60 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,10 +8,11 @@ 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/README.md)): [`docs/peterhinch-guis.md`](docs/peterhinch-guis.md) -(Nano-GUI / Micro-GUI / MicroPython-Touch), [`docs/tft-gui.md`](docs/tft-gui.md) -(@russhughes TFT GUI stub), [`docs/screenshots/`](docs/screenshots/README.md). +**Docs map** (the site's source is `docs/`, built with Sphinx; see +[`docs/index.md`](docs/index.md)): [`docs/guides/use-a-peter-hinch-gui.md`](docs/guides/use-a-peter-hinch-gui.md) +(Nano-GUI / Micro-GUI / MicroPython-Touch), [`docs/guides/run-the-tft-examples.md`](docs/guides/run-the-tft-examples.md) +(@russhughes TFT GUI stub), [`docs/reference/screenshots.md`](docs/reference/screenshots.md) +(images are in `docs/screenshots/`). Code-adjacent runbooks: [`tools/README.md`](tools/README.md) (example test matrix, PyScript/Playwright debugging, LVGL timer harnesses), [`lib/utils/README.md`](lib/utils/README.md), [`scripts/README.md`](scripts/README.md), diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..6f3da5035 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Contributing + +Examples, integrations and gallery work belong here. Fixes to the reusable +libraries and to hardware support belong in +[pydevices](https://github.com/PyDevices/pydevices). A new example, or a fix +to one that doesn't run on a board you have, is a good first contribution. + +## Where things are + +| Path | What's there | +|---|---| +| [`lib/examples/`](lib/examples/) | Portable examples and complete demo applications | +| [`lib/utils/`](lib/utils/) | Example helpers and third-party GUI adapters | +| [`.site/pyscript/`](.site/pyscript/) | The PyScript gallery and the browser demo runners | +| [`docs/`](docs/) | The documentation site's source, and the screenshots | +| [`tools/`](tools/) | Cross-interpreter example and LVGL test harnesses | +| [`packages/`](packages/) | GitHub MIP manifests for examples and helpers | +| [`tests/`](tests/) | Unit tests for the examples, utilities, gallery and tooling | + +## Set up and run the tests + +```bash +python3 -m venv .venv +.venv/bin/pip install --index-url https://test.pypi.org/simple/ \ + --extra-index-url https://pypi.org/simple/ -r requirements.txt -r requirements-dev.txt +.venv/bin/python -m unittest discover -s tests +.venv/bin/python -m pytest -s tests -q +.venv/bin/ruff check lib tests tools scripts +``` + +[`tools/README.md`](tools/README.md) describes the cross-interpreter example +matrix and the PyScript and Playwright debugging notes. [`AGENTS.md`](AGENTS.md) +lists the repository's conventions for application ownership and board configs. + +## Take a screenshot or a recording + +```bash +python tools/screenshot.py hello.py +python tools/record.py bouncing_balls 10 +``` + +Output goes to `docs/screenshots/` and `docs/videos/`. + +## Build the docs + +```bash +pip install -r docs/requirements.txt +sphinx-build -W --keep-going -b html docs _site +``` + +`-W` fails on any warning, as Read the Docs does. diff --git a/README.md b/README.md index da99fc19e..6852d5dea 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,12 @@ -

PyDevices Examples

+![A grey box with a yellow ellipse, the word Hello and a green line, drawn in a 320 by 480 window](docs/images/hello_shapes.png) -

See the portable PyDevices driver stack in action.

+pydevices-examples is the gallery of runnable programs for OmniPython: the same +scripts draw on MicroPython-compatible boards, CircuitPython-compatible boards, +a desktop window, a browser tab and an Android phone. -

- Documentation • - PyScript gallery • - Product packages • - Screenshots -

+## Install -| ![paint.py](https://raw.githubusercontent.com/PyDevices/pydevices-examples/main/docs/screenshots/paint.png) | ![tiny_toasters.py](https://raw.githubusercontent.com/PyDevices/pydevices-examples/main/docs/screenshots/tiny_toasters.gif) | -|:--:|:--:| -| [`paint.py`](lib/examples/paint.py) | [`tiny_toasters.py`](lib/examples/tiny_toasters/tiny_toasters.py) | - -This repository is the examples, integration documentation, and browser gallery -for the PyDevices driver ecosystem. The reusable product source lives in -**[pydevices](https://github.com/PyDevices/pydevices)**: - -- `displaydev` and `audiodev` provide portable display and audio interfaces. -- `events`, `keys`, and `multimer` provide shared event, key, and timing primitives. -- `appdev` is an optional application traffic controller for non-LVGL apps. -- `board_configs`, `board_peripherals`, and hardware drivers connect those interfaces - to real boards and desktop/browser hosts. - -Those libraries are designed to work across MicroPython, CircuitPython, and -CPython on microcontrollers, Linux, Windows, Android, browsers, and notebooks. -This repo demonstrates that portability; it is not the package source of truth. - -An ESP32-P4 can put any of these apps on a TV or a Windows laptop, with sound -and with the laptop's mouse and keyboard as its input: see -[Casting from a microcontroller](docs/casting.md). - -New here? Read the [newcomer's guide](docs/newcomers.md) for the fastest -entrypoints, application ownership, and examples-to-product boundary. - -> **Alpha quality.** The organization is being prepared for its first external -> users, so names and APIs may still evolve. - -## Try it - -The fastest route is the interactive -**[PyScript gallery](https://PyDevices.github.io/pydevices-examples/pyscript/)**. It runs -the real examples directly in a modern web browser. -To start a standalone browser app or offline PWA, use the -**[pyscript-template](https://github.com/PyDevices/pyscript-template)**; its -[bigger-app page](https://github.com/PyDevices/pyscript-template/blob/main/docs/packages.md) -turns piano and the drum machine into installable apps. - -To run an example on an Android phone without building anything, install the -prebuilt Runner and stage the script from this clone with `android.py`; see -[pydevices' Android page](https://github.com/PyDevices/pydevices/blob/main/docs/android.md#staging-a-script-with-androidpy). - -For a desktop clone (`--depth 1` skips the history, which is most of the -download: about 67 MB instead of 270 MB): +On a desktop: ```bash git clone --depth 1 https://github.com/PyDevices/pydevices-examples.git @@ -60,142 +14,47 @@ cd pydevices-examples python3 -m venv .venv .venv/bin/pip install --index-url https://test.pypi.org/simple/ \ --extra-index-url https://pypi.org/simple/ -r requirements.txt -cd lib -../.venv/bin/python examples/pydevices_demo.py ``` -On Windows, use the equivalent `.venv\Scripts\python.exe` commands. - -Examples that import helpers from `lib/utils` (see -[lib/utils/README.md](lib/utils/README.md)) need `utils/` on the import path. -Set `PYTHONPATH` before running from `lib/`: - -```bash -cd lib -PYTHONPATH=.:lib:utils ../.venv/bin/python examples/hello.py -``` - -For MicroPython, install unprefixed MIP package names from the PyDevices -MIP index, then install a board package: +On a MicroPython-compatible board: ```python import mip -INDEX = "https://PyDevices.github.io/mip" -mip.install("pydevices", index=INDEX) # displaydev, appdev, and the rest of lib/ -``` - -See the -[pydevices install workflows](https://github.com/PyDevices/pydevices/blob/main/docs/install-workflows.md) -and [board configs](https://github.com/PyDevices/pydevices/blob/main/docs/board-configs.md) -for complete device setup. - -To run the examples on MicroPython on your desktop, build it with -[micropython-pydevices](https://github.com/PyDevices/micropython-pydevices), -using its `pydevices` variant and every module. That variant freezes the -desktop board config and, on Windows, `uwin32`, which the Windows display -and audio backends need. On Windows, build from PowerShell as its -[Windows steps](https://github.com/PyDevices/micropython-pydevices/blob/main/docs/newcomers.md#on-windows) -say, then run an example from `lib/`: - -```powershell -python build_mp.py --port windows --variant pydevices --modules all # in micropython-pydevices -cd pydevices-examples\lib -..\..\micropython-pydevices\builds\windows\pydevices\micropython.exe -X heapsize=64M examples\hello.py +mip.install("pydevices", index="https://PyDevices.github.io/mip") ``` -On Linux the same build with `--port unix` gives -`builds/unix/pydevices/micropython`. `micropython.exe` already has `lib` and -`utils` on its path, so it needs no `MICROPYPATH`; if you set one on Windows, -separate the entries with `;`, not `:`. - -## App ownership +## Example -Board configs describe hardware. They export neutral pieces such as -`display_drv`, `touch_read`, and `host_read`; they do not create -an application-level `appdev.App`. - -Non-LVGL examples in this repo opt into the optional `appdev` coordinator: +Save this as `hello_shapes.py` and run `.venv/bin/python hello_shapes.py`: ```python import board_config -from board_config import display_drv import appdev - -app = appdev.App(board_config) +import pygraphics +from palettes import get_palette + +display = board_config.display_drv +pal = get_palette() +w, h = display.width, display.height +print(f"drawing on a {w}x{h} display") + +pygraphics.fill(display, pal.BLACK) +pygraphics.fill_rect(display, w // 6, h // 3, w * 2 // 3, h // 3, pal.GREY) +pygraphics.ellipse(display, w // 2, h // 2, w // 4, h // 8, pal.YELLOW, True) +pygraphics.line(display, 0, 0, w - 1, h - 1, pal.GREEN) +pygraphics.text(display, "Hello", (w - 40) // 2, h // 2 - 4, pal.BLACK) +display.show() + +app = appdev.App(board_config) # keeps the window open until you close it ``` -The app keeps itself alive past the end of the script, so no trailing -`app.run()` is needed. Call it only when you want the script to block at that -point, or need a nonzero exit code. - -LVGL examples use the coordinator bundled with the LVGL binding and do not -import `appdev`: +It opens the window at the top of this page and prints: -```python -from board_config import display_drv -from display_driver import app -``` - -This separation keeps hardware policy out of board configs and lets applications -provide a different event loop or traffic controller when appropriate. - -## Package names - -Python distributions on TestPyPI use the organization prefix; imports and MIP -packages remain conventional and unprefixed. `pydevices` ships all of the core -`lib/` tree as one distribution, and the MIP index publishes it the same way -- -a single `pydevices` package, not one per component. - -| TestPyPI distribution | Python import / MIP name | -|---|---| -| `pydevices` | `displaydev`, `audiodev`, `appdev`, `events`, `keys`, `multimer`, `boarddev` | -| `pydevices-pygraphics` | `pygraphics` | -| `pydevices-palettes` | `palettes` | -| `pydevices-pdwidgets` | `pdwidgets` | -| `pydevices-lvgl` | `lvgl` | - -## Repository layout - -| Path | Purpose | -|---|---| -| [`lib/examples/`](lib/examples/) | Portable examples and complete demo applications | -| [`lib/utils/`](lib/utils/) | Example helpers and third-party GUI adapters | -| [`.site/pyscript/`](.site/pyscript/) | PyScript gallery and browser demo runners | -| [`docs/`](docs/) | Peter Hinch GUI integration, the TFT GUI stub, and screenshots | -| [`tools/`](tools/) | Cross-interpreter example and LVGL test harnesses | -| [`packages/`](packages/) | GitHub MIP manifests for examples and helpers | - -## Related repositories - -- [pydevices](https://github.com/PyDevices/pydevices) — canonical product source and core driver package home -- [mip](https://github.com/PyDevices/mip) — PyDevices MIP index fork -- [lvgl-bindings](https://github.com/PyDevices/lvgl-bindings) — shared LVGL binding source (LVGL's `display_driver` is in pydevices) -- [lvgl-micropython](https://github.com/PyDevices/lvgl-micropython), - [lvgl-circuitpython](https://github.com/PyDevices/lvgl-circuitpython), and - [lvgl-python](https://github.com/PyDevices/lvgl-python) — interpreter-specific LVGL distributions -- [pygraphics](https://github.com/PyDevices/pygraphics), - [palettes](https://github.com/PyDevices/palettes), and - [pdwidgets](https://github.com/PyDevices/pdwidgets) — companion packages -- [android-runner](https://github.com/PyDevices/android-runner) — the prebuilt Runner APK that `android.py` stages examples onto -- [android-template](https://github.com/PyDevices/android-template) — Android application packaging -- [pyscript-template](https://github.com/PyDevices/pyscript-template) — standalone PyScript application template - -What's planned next is in [ROADMAP.md](ROADMAP.md). - -## Development - -```bash -.venv/bin/python -m unittest discover -s tests -.venv/bin/python -m pytest -s tests -q -.venv/bin/ruff check lib tests tools scripts +```text +drawing on a 320x480 display ``` -See [AGENTS.md](AGENTS.md) and [tools/README.md](tools/README.md) for the -cross-interpreter example matrix. Contributions to reusable libraries and hardware -support belong in pydevices; examples, integrations, and gallery work -belong here. - -## License +[Docs](https://github.com/PyDevices/pydevices-examples/blob/main/docs/index.md) · [Examples](https://pydevices.github.io/pydevices-examples/pyscript/) · [Roadmap](ROADMAP.md) -MIT. See [LICENSE](LICENSE). +Made by [PyDevices](https://pydevices.com). diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 80241bfa5..000000000 --- a/docs/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# pydevices-examples documentation - -This repository is a **showcase**, not a product. The product — the board -contract, core packages, drivers, installation, and platform notes — is -documented in [pydevices/docs](https://github.com/PyDevices/pydevices/tree/main/docs). - -What lives here: - -- [peterhinch-guis.md](peterhinch-guis.md) — running Peter Hinch's Nano-GUI, - Micro-GUI, and MicroPython-Touch on PyDevices hardware. -- [tft-gui.md](tft-gui.md) — the @russhughes TFT GUI stub. -- [screenshots/](screenshots/README.md) — captured example output. - -Code-adjacent docs: - -- [tools/README.md](../tools/README.md) — the example test matrix, PyScript and - Playwright debugging, the LVGL timer harnesses, and IDE typings. -- [lib/utils/README.md](../lib/utils/README.md) — the optional `utils` modules. -- [scripts/README.md](../scripts/README.md), [tests/README.md](../tests/README.md) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..023d1cb1f --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,21 @@ +"""Sphinx configuration for the pydevices-examples documentation. + +Everything shared by the PyDevices documentation projects (theme, top bar, +Markdown and docstring setup, the reference map) comes from pydevices-docs. +""" + +from pydevices_docs import configure + +globals().update( + configure( + "pydevices-examples", + __file__, + repo="PyDevices/pydevices-examples", + edit_path="docs", + version_file=None, + ) +) + +# index.md is plain Markdown so the docs read on GitHub too; the site +# navigation lives in contents.md. +master_doc = "contents" diff --git a/docs/contents.md b/docs/contents.md new file mode 100644 index 000000000..4bc65db21 --- /dev/null +++ b/docs/contents.md @@ -0,0 +1,39 @@ +# Contents + +The site's navigation. On GitHub, start at [the docs index](index.md). + +```{toctree} +:hidden: +:caption: Get started + +index +get-started/install +get-started/first-program +``` + +```{toctree} +:hidden: +:caption: Guides + +guides/run-in-the-browser +guides/cast-to-a-tv +guides/use-a-peter-hinch-gui +guides/run-the-tft-examples +``` + +```{toctree} +:hidden: +:caption: Reference + +reference/package-names +reference/cast-runtimes +reference/screenshots +``` + +```{toctree} +:hidden: +:caption: How it works + +how-it-works/index +how-it-works/casting +``` diff --git a/docs/get-started/first-program.md b/docs/get-started/first-program.md new file mode 100644 index 000000000..c7a77f855 --- /dev/null +++ b/docs/get-started/first-program.md @@ -0,0 +1,61 @@ +# Your first program + +This draws a few shapes and a word on the display your `board_config` names. +On a desktop that is a window at the desktop config's 320 x 480. + +![The window the program opens: a grey box with a yellow ellipse, the word Hello, and a green line across the window](../images/hello_shapes.png) + +Install the packages first ([Install](install.md)), then save this as +`hello_shapes.py`: + +```python +import board_config +import appdev +import pygraphics +from palettes import get_palette + +display = board_config.display_drv +pal = get_palette() +w, h = display.width, display.height +print(f"drawing on a {w}x{h} display") + +pygraphics.fill(display, pal.BLACK) +pygraphics.fill_rect(display, w // 6, h // 3, w * 2 // 3, h // 3, pal.GREY) +pygraphics.ellipse(display, w // 2, h // 2, w // 4, h // 8, pal.YELLOW, True) +pygraphics.line(display, 0, 0, w - 1, h - 1, pal.GREEN) +pygraphics.text(display, "Hello", (w - 40) // 2, h // 2 - 4, pal.BLACK) +display.show() + +app = appdev.App(board_config) # keeps the window open until you close it +``` + +Run it from the folder you saved it in: + +```bash +.venv/bin/python hello_shapes.py +``` + +It prints the size of the display and opens the window above: + +```text +drawing on a 320x480 display +``` + +## What each part does + +`board_config` is the module that describes your hardware. On a desktop the +`pydevices-desktop` package supplies one; on a board, a board package does. +Its `display_drv` is the display, so the same script draws on a window or on a +panel. `pygraphics` draws into it and `palettes` names the colors. +`display.show()` pushes the drawing to the screen. + +`appdev.App(board_config)` is the optional coordinator the non-LVGL examples +use for events and timers. It keeps the program alive after the last line, so +the window stays open. [Application ownership](../how-it-works/index.md#application-ownership) +says why the examples build it themselves. + +## Next + +Run `lib/examples/pydevices_demo.py` from a clone, then pick another from +[`lib/examples/`](https://github.com/PyDevices/pydevices-examples/tree/main/lib/examples). +The [screenshots](../reference/screenshots.md) show what many of them draw. diff --git a/docs/get-started/install.md b/docs/get-started/install.md new file mode 100644 index 000000000..71f41933b --- /dev/null +++ b/docs/get-started/install.md @@ -0,0 +1,83 @@ +# Install + +The examples are plain scripts in +[`lib/examples/`](https://github.com/PyDevices/pydevices-examples/tree/main/lib/examples). +You run them from a clone, once the PyDevices packages are installed. + +## On a desktop + +`--depth 1` skips the history, which is most of the download: about 67 MB +instead of 270 MB. + +```bash +git clone --depth 1 https://github.com/PyDevices/pydevices-examples.git +cd pydevices-examples +python3 -m venv .venv +.venv/bin/pip install --index-url https://test.pypi.org/simple/ \ + --extra-index-url https://pypi.org/simple/ -r requirements.txt +``` + +On Windows, use the same commands with `.venv\Scripts\python.exe` and +`.venv\Scripts\pip.exe`. + +Run an example from `lib/`: + +```bash +cd lib +../.venv/bin/python examples/pydevices_demo.py +``` + +Examples that import helpers from `lib/utils` say so in a `# utils:` comment at +the top. They need `utils/` on the import path: + +```bash +cd lib +PYTHONPATH=.:lib:utils ../.venv/bin/python examples/hello.py +``` + +## On MicroPython-compatible boards + +Install the PyDevices packages from the PyDevices MIP index, then a board +package for your board: + +```python +import mip + +INDEX = "https://PyDevices.github.io/mip" +mip.install("pydevices", index=INDEX) # displaydev, appdev, and the rest of lib/ +``` + +[Install workflows](https://github.com/PyDevices/pydevices/blob/main/docs/install-workflows.md) +and [board configs](https://github.com/PyDevices/pydevices/blob/main/docs/board-configs.md) +in the pydevices docs cover the board package and complete device setup. + +## On MicroPython on your desktop + +Build MicroPython with +[micropython-pydevices](https://github.com/PyDevices/micropython-pydevices), +using its `pydevices` variant and every module. That variant freezes the desktop +board config and, on Windows, `uwin32`, which the Windows display and audio +backends need. On Windows, build from PowerShell as its +[Windows steps](https://github.com/PyDevices/micropython-pydevices/blob/main/docs/guides/build-on-windows.md) +say, then run an example from `lib/`: + +```powershell +python build_mp.py --port windows --variant pydevices --modules all # in micropython-pydevices +cd pydevices-examples\lib +..\..\micropython-pydevices\builds\windows\pydevices\micropython.exe -X heapsize=64M examples\hello.py +``` + +On Linux the same build with `--port unix` gives `builds/unix/pydevices/micropython`. +`micropython.exe` already has `lib` and `utils` on its path, so it needs no +`MICROPYPATH`. If you set one on Windows, separate the entries with `;`, not `:`. + +## In a browser or on a phone + +Nothing to install for the browser: the +[PyScript gallery](https://PyDevices.github.io/pydevices-examples/pyscript/) +runs the real example scripts in a modern web browser. See +[Run an example in the browser](../guides/run-in-the-browser.md). + +On an Android phone, install the prebuilt Runner and stage a script from your +clone with `android.py`; see +[pydevices' Android page](https://github.com/PyDevices/pydevices/blob/main/docs/android.md#staging-a-script-with-androidpy). diff --git a/docs/casting.md b/docs/guides/cast-to-a-tv.md similarity index 60% rename from docs/casting.md rename to docs/guides/cast-to-a-tv.md index 38515041f..0c67db99c 100644 --- a/docs/casting.md +++ b/docs/guides/cast-to-a-tv.md @@ -1,4 +1,4 @@ -# Casting a PyDevices app to a TV +# Cast an app to a TV An ESP32-P4 puts its screen on a 65" TV, and in a window on a Windows laptop, over your own Wi-Fi. The TV gets the sound too, in time with the picture. On @@ -10,16 +10,16 @@ the video. Any PyDevices app can do this without changing a line. Each method below has a `board_config.py`: put its folder first on the path and run the app, LVGL or not, and the app's display is the TV. The same configs run on a desktop too: -[every runtime](#every-runtime) says which methods work where. +the [runtime table](../reference/cast-runtimes.md) says which methods work where. ## Which way to cast | Method | The screen | What the screen needs | Sound | Input back | Boards | From a desktop | |---|---|---|---|---|---|---| -| [Miracast to a Roku](#miracast-to-a-roku-tv) | Roku TV | Screen mirroring on, nothing installed | yes, 48 kHz, in sync | not yet ([#168](https://github.com/PyDevices/pydevices-examples/issues/168)) | ESP32-P4 | CPython with ffmpeg | +| [Miracast to a Roku](#miracast-to-a-roku-tv) | Roku TV | Screen mirroring on, nothing installed | yes, 48 kHz, in sync | no ([#168](https://github.com/PyDevices/pydevices-examples/issues/168)) | ESP32-P4 | CPython with ffmpeg | | [Miracast to Windows](#miracast-to-a-windows-laptop) | Windows laptop | the Wireless Display app open | yes | the laptop's mouse and keyboard | ESP32-P4 | CPython with ffmpeg, from another PC; glitches on a busy network | -| [The Companion channel](#the-pydevices-companion-channel) | Roku TV | our channel, sideloaded once | through the TV | the TV's remote | any board with Wi-Fi and `pngio` | CPython and MicroPython | -| [HLS video](#hls-video-to-a-roku-or-vlc) | Roku TV, or VLC anywhere | the Companion channel, or any HLS player | not yet (needs AAC) | none | ESP32-P4 | CPython with ffmpeg | +| [The Companion channel](#the-pydevices-companion-channel) | Roku TV | the Companion channel, sideloaded once | through the TV | the TV's remote | any board with Wi-Fi and `pngio` | CPython and MicroPython | +| [HLS video](#hls-video-to-a-roku-or-vlc) | Roku TV, or VLC anywhere | the Companion channel, or any HLS player | no (needs AAC) | none | ESP32-P4 | CPython with ffmpeg | In short: **Miracast** for a live picture with sound, on a TV or a laptop, with nothing installed on either. **The Companion channel** for a TV you can play @@ -28,9 +28,8 @@ stream that a TV and any number of VLC windows can all watch. The ESP32-P4 is the only board with an H.264 encoder, so on a board both Miracast methods and HLS need one. They're proven on the -[Waveshare ESP32-P4 4" panel](https://github.com/PyDevices/micropython-pydevices/blob/main/docs/newcomers.md#boards-we-build-for). -The P4 WIFI6 DEV-KIT builds the same firmware but hasn't cast yet. The firmware -needs `castif`, `h264enc` and `tsmux`, all in `--modules all`. +[Waveshare ESP32-P4 4" panel](https://github.com/PyDevices/micropython-pydevices/blob/main/docs/reference/boards.md#boards-we-build-for). +The firmware needs `castif`, `h264enc` and `tsmux`, all in `--modules all`. ## Miracast to a Roku TV @@ -45,20 +44,19 @@ import lv_test_timer # or any app The picture is the panel's 720x720 in the middle of the TV's 1280x720, at about 24 fps. A P4 with no screen of its own casts the full 1280x720 at 18 fps -([`cast/headless_cast.py`](../lib/examples/cast/headless_cast.py)). Sound the -P4 plays goes with it as 48 kHz audio on the picture's clock. A ten-minute -drum machine cast drifted about 1 ms, and the drum hits landed with the step -light. +([`cast/headless_cast.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/examples/cast/headless_cast.py)). Sound the +P4 plays goes with it as 48 kHz audio on the picture's clock, so a drum +machine's hits land with its step light. The P4 also controls the TV over its own network API (ECP, built into every Roku, with **Control by mobile apps** on). It can power the TV on, change the input or the volume, and press any key. -[`cast/tv_cast.py`](../lib/examples/cast/tv_cast.py) turns the TV on and -casts a melody. The [house panel](../lib/examples/house/) has a button that +[`cast/tv_cast.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/examples/cast/tv_cast.py) turns the TV on and +casts a melody. The [house panel](https://github.com/PyDevices/pydevices-examples/tree/main/lib/examples/house/) has a button that puts the whole panel on the TV. -Examples: [`cast/`](../lib/examples/cast/README.md), -[`miracast/board_config.py`](../lib/examples/miracast/board_config.py) for any +Examples: [`cast/`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/examples/cast/README.md), +[`miracast/board_config.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/examples/miracast/board_config.py) for any app. ## Miracast to a Windows laptop @@ -66,14 +64,14 @@ app. Open the laptop's **Wireless Display** app (Settings > System > Projecting to this PC) and the P4 appears in a window. The same `miracast/board_config.py` works with `KIND = "windows"`. The sink can be the laptop's name, such as -`windows:elitebook`, where the runtime's resolver knows it (Windows' +`windows:my-laptop`, where the runtime's resolver knows it (Windows' `python.exe` does; WSL doesn't, so give it the address). This one talks back. Windows sends the laptop's mouse and keyboard to the board (Miracast's UIBC), and `CastDisplay` turns them into touch and key events. An app on the P4 can't tell them from its own touch screen: testris plays from the laptop's keyboard, and -[`cast/laptop_input.py`](../lib/examples/cast/laptop_input.py) draws the +[`cast/laptop_input.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/examples/cast/laptop_input.py) draws the cursor and a dot per click. The Wireless Display window has to have focus: it then takes the mouse pointer and the keys, and the Windows key gives them back. @@ -81,29 +79,30 @@ back. **Expect glitches on a busy 2.4 GHz network.** Miracast sends its video over UDP, so a packet Wi-Fi loses is gone. Windows draws the damage instead of hiding it: a band of smeared or discoloured blocks, or a pink or grey flash, -until the board sends a fresh keyframe a moment later. On this bench, with -about 0.5 % of packets lost, that was one every two to three seconds, the same -from the P4 and from CPython and at any bitrate. A quieter channel, or the +until the board sends a fresh keyframe a moment later. How often depends on +how many packets the network loses, the same from the P4 and from CPython and at +any bitrate. A quieter channel, or the board and laptop closer to the router, means fewer. `CastDisplay.stats()` counts them as `idr_requests`, the keyframes the laptop asked for after a loss. ## The PyDevices Companion channel -A small Roku channel of ours, in -[`tools/roku_companion_app`](../tools/roku_companion_app/README.md), +The Companion channel is a small Roku channel, in +[`tools/roku_companion_app`](https://github.com/PyDevices/pydevices-examples/blob/main/tools/roku_companion_app/README.md), sideloaded once with the TV in developer mode. Then a program on the network can use the TV as a display, read its remote, speak through it and play video on it. - **The TV as a display.** `RokuDisplay` is a framebuffer display whose frames - show full-screen, sent as PNGs. At 480x270 a desktop gets 8 to 13 frames a - second onto the TV, and a P4 about 2. + show full-screen, sent as PNGs. At 480x270 a desktop gets 6 to 10 frames a + second onto the TV, and a P4 about 2. The TV fetching each PNG sets that + limit: the program itself draws at 28 to 29. - **The remote as input.** All eleven buttons a channel can see arrive as key presses and releases, and a held button repeats. testris plays from the - couch through [`roku/board_config.py`](../lib/examples/roku/board_config.py). + couch through [`roku/board_config.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/examples/roku/board_config.py). - **Speech, a dashboard, and frames from any URL**: `say()`, `dashboard()` and `frames(url)`, which shows the PNG or JPEG a URL serves, over and over. - One example each in [`lib/examples/roku/`](../lib/examples/roku/). + One example each in [`lib/examples/roku/`](https://github.com/PyDevices/pydevices-examples/tree/main/lib/examples/roku/). - **Video**: `video(url)` plays HLS, which is how the next method reaches a TV. @@ -135,55 +134,12 @@ gets the same stream, so a TV and a laptop can watch at once. VLC fetched every segment in order for 30 s with the LVGL analog clock and with two of the portal's non-LVGL hero apps, polyhedron and radar_scope. -HLS is video only. A Roku plays HLS audio only as AAC, and there's no AAC -encoder in the firmware yet. +HLS is video only. A Roku plays HLS audio only as AAC, and the firmware has no AAC +encoder. On a desktop, the same `roku_hls/board_config.py` runs under CPython and ffmpeg makes the stream. -## Every runtime - -Each method on each runtime that could run it, with real examples through -their `board_config.py`. The numbers are frames a second reaching the screen, -for testris and for the LVGL `lv_test_timer`. - -| Runtime | Companion channel | Miracast to a Roku | Miracast to Windows | HLS | -|---|---|---|---|---| -| `python` (Linux) | 12.7, 11.3 | 30, 30 | 30 | plays | -| `python.exe` (Windows) | 12.2, 10.6 | 30, 30 | 30 | plays | -| `micropython` (unix) | 11.8, 7.4 | no H.264 | no H.264 | no H.264 | -| `micropython.exe` (Windows) | 8.4, 7.7 | no H.264 | no H.264 | no H.264 | -| ESP32-P4 4" panel | 2.2, 2.1 | 29, 18 | 30 | plays | -| ESP32-S3 LCD-7 | 1.8, 1.7 | no H.264 | no H.264 | no H.264 | - -**No H.264** is by design. Desktop MicroPython and the S3 have no H.264 -encoder, and adding one isn't planned, so `CastDisplay` and `HlsDisplay` stop -there with an error that says so. On CPython, ffmpeg does the encoding: put it -on the PATH, or name it in the `FFMPEG` environment variable (Windows -`python.exe` started from WSL needs that). The Miracast stream from CPython is -the same MPEG-TS over RTP that the P4 sends, byte for byte. - -**Miracast to Windows** is testris only, with glitches from Wi-Fi loss (see -[Miracast to a Windows laptop](#miracast-to-a-windows-laptop)). From CPython -it needs a second PC as the receiver: Windows won't take a cast from itself. - -On a desktop, a frame costs a few milliseconds of PNG encoding. On the P4 it's -about 180 ms at 480x270 and on the S3 about 380 ms, which is why the boards get -2 frames a second over the Companion channel and 18 to 29 over Miracast, where the P4's H.264 encoder -does the work. - -To run any of these from `lib/`, with the TV's address: - -```bash -python ../tools/run_cast_example.py roku testris TV_ADDRESS -``` - -The first argument is the method's folder (`roku`, `miracast` or `roku_hls`), -and `micropython`, `python.exe` and `micropython.exe` run it the same way. -Without the runner, set `CAST_TARGET` to the address, put the method's folder -first on the path and run the example. A board runs the same script through -mpftp, with its settings at the top of the file. - ## Let AutoDisplay pick a cast display With PyDevices 0.7.7 or later (experimental), the Roku, HLS and Miracast @@ -196,7 +152,7 @@ yourself: ```python import backends backends.require("display", "hls", module="hls_display") # or "roku", "cast" -display_drv = AutoDisplay(1280, 720, tv="192.168.1.129") # keywords go to the display +display_drv = AutoDisplay(1280, 720, tv="TV_ADDRESS") # keywords go to the display ``` The names are `roku` (`tv=`), `hls` (`tv=`, or `tv=None` to open the printed @@ -207,21 +163,22 @@ earlier PyDevices there is no registry and the configs work as they always did. The registry itself is described in the [displaydev guide](https://github.com/PyDevices/pydevices/blob/main/docs/displaydev.md#experimental-your-own-backend). -## What isn't done yet +## Run a cast example + +To run any of these from `lib/`, with the TV's address: + +```bash +python ../tools/run_cast_example.py roku testris TV_ADDRESS +``` + +The first argument is the method's folder (`roku`, `miracast` or `roku_hls`), +and `micropython`, `python.exe` and `micropython.exe` run it the same way. +Without the runner, set `CAST_TARGET` to the address, put the method's folder +first on the path and run the example. A board runs the same script through +mpftp, with its settings at the top of the file. -The Roku's remote as input over Miracast is -[#168](https://github.com/PyDevices/pydevices-examples/issues/168). +[How casting works](../how-it-works/casting.md) describes the Miracast session and the encoder pipeline. -## How it works +## The Roku remote over Miracast -The [cast README](../lib/examples/cast/README.md#how-it-works) covers the -Miracast session (Wi-Fi Display over infrastructure, MICE and RTSP, in -Python), and the C task that streams MPEG-TS over RTP with the audio on the -same clock. The encoders and the muxer are firmware modules of their own: -[`h264enc`](https://github.com/PyDevices/micropython-pydevices/tree/main/modules/h264enc), -[`tsmux`](https://github.com/PyDevices/micropython-pydevices/tree/main/modules/tsmux) -and the PPA's colour conversion, -[`ppa`](https://github.com/PyDevices/micropython-pydevices/tree/main/modules/ppa). -The Companion channel's BrightScript is in -[`tools/roku_companion_app`](../tools/roku_companion_app/README.md), and its -Python side in [`lib/utils/roku_companion.py`](../lib/utils/roku_companion.py). +The Roku's remote doesn't reach the app over Miracast. It is tracked in [#168](https://github.com/PyDevices/pydevices-examples/issues/168) and listed on the [roadmap](https://github.com/PyDevices/pydevices-examples/blob/main/ROADMAP.md). diff --git a/docs/guides/run-in-the-browser.md b/docs/guides/run-in-the-browser.md new file mode 100644 index 000000000..95bfe6d94 --- /dev/null +++ b/docs/guides/run-in-the-browser.md @@ -0,0 +1,28 @@ +# Run an example in the browser + +The [PyScript gallery](https://PyDevices.github.io/pydevices-examples/pyscript/) +runs the real scripts from `lib/examples/` in a modern web browser. Open it, +pick an example and it runs; there is nothing to install. The same example +that opens a window on your desktop draws on a canvas in the page. + +## Turn an example into an app + +To start a standalone browser app or an offline PWA, use the +[pyscript-template](https://github.com/PyDevices/pyscript-template). Its +[bring in a bigger app](https://github.com/PyDevices/pyscript-template/blob/main/docs/guides/bring-in-a-bigger-app.md) +page turns the piano and the drum machine into installable apps. + +## Edit an example and see it in the gallery + +`.site/pyscript/lib` is a symlink to `lib/`, so a change to a script under +`lib/` shows up in the gallery. From a clone, serve the repository with the +development server and open the gallery on it: + +```bash +python tools/serve.py +# then open http://127.0.0.1:8000/.site/pyscript/index.html +``` + +The server sends the cross-origin-isolation headers that PyScript's worker +pages need, the way the published site does. Browser debugging notes are in +[`tools/README.md`](https://github.com/PyDevices/pydevices-examples/blob/main/tools/README.md). diff --git a/docs/guides/run-the-tft-examples.md b/docs/guides/run-the-tft-examples.md new file mode 100644 index 000000000..3b3a04ab9 --- /dev/null +++ b/docs/guides/run-the-tft-examples.md @@ -0,0 +1,20 @@ +# Run the TFT examples + +Many of the examples were ported from [@russhughes st7789py_mpy](https://github.com/russhughes/st7789py_mpy). + +## Config + +Use or adapt [`lib/utils/tft_config.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/utils/tft_config.py). If an example fails to import its config, compare it with that file. + +## Font and image tools + +- [text_font_converter.py](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) → `tft_text.text()` +- [write_font_converter.py](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) → `tft_write.write()` +- [image_converter.py](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) → `.bitmap()` + +See the pygraphics [docs](https://pygraphics.readthedocs.io) for drawing text and loading bitmaps. + +## Notable examples + +`tiny_hello.py`, `feathers.py`, `proverbs/`, `chango/` and `tiny_toasters/`, a game port, are under +[`lib/examples/`](https://github.com/PyDevices/pydevices-examples/tree/main/lib/examples/). diff --git a/docs/peterhinch-guis.md b/docs/guides/use-a-peter-hinch-gui.md similarity index 62% rename from docs/peterhinch-guis.md rename to docs/guides/use-a-peter-hinch-gui.md index 94aadfafd..e1bd844f2 100644 --- a/docs/peterhinch-guis.md +++ b/docs/guides/use-a-peter-hinch-gui.md @@ -1,4 +1,4 @@ -# Peter Hinch GUIs +# Use a Peter Hinch GUI Three of [Peter Hinch](https://github.com/peterhinch)'s MicroPython GUI libraries run unchanged on PyDevices hardware, which makes them the clearest @@ -7,18 +7,19 @@ page: [peterhinch.html](https://pydevices.github.io/pydevices-examples/gallery/p | GUI | Input style | Setup module | |---|---|---| -| [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) | Display-only, no input — for memory-constrained boards | [`color_setup.py`](../lib/utils/color_setup.py) | -| [Micro-GUI](https://github.com/peterhinch/micropython-micro-gui) | Buttons / encoder, callback-style widgets (asyncio underneath) | [`hardware_setup.py`](../lib/utils/hardware_setup.py) | -| [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) | Touch widgets and async UI (developed from Micro-GUI) | [`touch_setup.py`](../lib/utils/touch_setup.py) | +| [Nano-GUI](https://github.com/peterhinch/micropython-nano-gui) | Display-only, no input — for memory-constrained boards | [`color_setup.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/utils/color_setup.py) | +| [Micro-GUI](https://github.com/peterhinch/micropython-micro-gui) | Buttons / encoder, callback-style widgets (asyncio underneath) | [`hardware_setup.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/utils/hardware_setup.py) | +| [MicroPython-Touch](https://github.com/peterhinch/micropython-touch) | Touch widgets and async UI (developed from Micro-GUI) | [`touch_setup.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/utils/touch_setup.py) | ## How the integration works This repository does **not** vendor any of the three. Importing the setup module for the GUI you want calls -[`fetch_ph_gui`](../lib/utils/fetch_ph_gui.py), -which `mip`-installs the upstream `gui/` tree into `lib/utils/gui/` and patches -its `pygraphics.FrameBuffer` isinstance checks. Display wiring goes through -[`displaybuf.py`](../lib/utils/displaybuf.py), +[`fetch_ph_gui`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/utils/fetch_ph_gui.py), +which `mip`-installs the upstream `gui/` tree into `lib/utils/gui/` and, on a +desktop, applies in-memory compatibility patches for time, `machine.Pin`, +`asyncio` and `micropython.const`. It makes no edits under `gui/`. Display wiring goes through +[`displaybuf.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/utils/displaybuf.py), which supplies the `ssd` framebuffer the GUIs expect. **Only one Hinch GUI may occupy `utils/gui/` at a time.** `fetch_ph_gui` empties @@ -34,11 +35,11 @@ application-owned `appdev` app. | Component | Location | Notes | |---|---|---| -| `board_config.py` | `pydevices/board_configs/` | Display plus neutral input capabilities | +| `board_config.py` | pydevices' `board_configs/` | Display plus neutral input capabilities | | *setup module* | `lib/utils/` | `color_setup.py`, `hardware_setup.py`, or `touch_setup.py` — see the table above | -| `fetch_ph_gui.py` | `lib/utils/` | `mip` install into `utils/gui/` plus the FrameBuffer patches | +| `fetch_ph_gui.py` | `lib/utils/` | `mip` install into `utils/gui/` plus the desktop compatibility patches | | `displaybuf.py` | `lib/utils/` | The `ssd` framebuffer | -| [`uctypes.py`](../lib/utils/uctypes.py) | `lib/utils/` | CircuitPython shim for the GUIs' `writer.py` | +| [`uctypes.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/utils/uctypes.py) | `lib/utils/` | CircuitPython shim for the GUIs' `writer.py` | | `gui/` | `lib/utils/gui/` | **Upstream** — installed by the fetch, not in git | ### Installing manually @@ -61,9 +62,9 @@ loader that fetches the GUI through its setup module: | GUI | Example | Gallery | |---|---|---| -| Nano-GUI | [`nano_gui_simpletest.py`](../lib/examples/nano_gui_simpletest.py) | [loader](https://pydevices.github.io/pydevices-examples/gallery/micropython.html?modules=nano_gui_simpletest) | -| Micro-GUI | [`micro_gui_simpletest.py`](../lib/examples/micro_gui_simpletest.py) | [loader](https://pydevices.github.io/pydevices-examples/gallery/micropython.html?modules=micro_gui_simpletest) | -| MicroPython-Touch | [`touch_gui_simpletest.py`](../lib/examples/touch_gui_simpletest.py) | [loader](https://pydevices.github.io/pydevices-examples/gallery/micropython.html?modules=touch_gui_simpletest) | +| Nano-GUI | [`nano_gui_simpletest.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/examples/nano_gui_simpletest.py) | [loader](https://pydevices.github.io/pydevices-examples/gallery/micropython.html?modules=nano_gui_simpletest) | +| Micro-GUI | [`micro_gui_simpletest.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/examples/micro_gui_simpletest.py) | [loader](https://pydevices.github.io/pydevices-examples/gallery/micropython.html?modules=micro_gui_simpletest) | +| MicroPython-Touch | [`touch_gui_simpletest.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/examples/touch_gui_simpletest.py) | [loader](https://pydevices.github.io/pydevices-examples/gallery/micropython.html?modules=touch_gui_simpletest) | ```bash cd pydevices-examples/lib @@ -84,25 +85,25 @@ routine. For development against an editable upstream checkout, clone the whole ```bash curl -sL https://github.com/peterhinch/micropython-nano-gui/archive/refs/heads/master.tar.gz \ - | tar xz --strip-components=2 -C src/utils micropython-nano-gui-master/gui + | tar xz --strip-components=2 -C lib/utils micropython-nano-gui-master/gui ``` ### Micro-GUI -`hardware_setup.py` builds `Display` from `appdev.App` devices. Desktop -navigation defaults: +`hardware_setup.py` builds `Display` from `appdev.App` devices. On a desktop, +the keys are: | Key | Action | |---|---| -| Tab / Right | Next control | +| Right | Next control | | Left | Previous control | -| Enter / Space | Select | +| Space | Select | | Up / Down | Increase / decrease | ### MicroPython-Touch -Upstream renamed `hardware_setup.py` → `touch_setup.py` in December 2024, and -this repository follows that name. `touch_setup.py` wires a mouse/touch `Poller` +Upstream renamed `hardware_setup.py` to `touch_setup.py`, and this repository +follows that name. `touch_setup.py` wires a mouse/touch `Poller` into `Display(ssd, tpad)`: ```python diff --git a/docs/how-it-works/casting.md b/docs/how-it-works/casting.md new file mode 100644 index 000000000..b69ff7910 --- /dev/null +++ b/docs/how-it-works/casting.md @@ -0,0 +1,13 @@ +# How casting works + +The [cast README](https://github.com/PyDevices/pydevices-examples/blob/main/lib/examples/cast/README.md#how-it-works) covers the +Miracast session (Wi-Fi Display over infrastructure, MICE and RTSP, in +Python), and the C task that streams MPEG-TS over RTP with the audio on the +same clock. The encoders and the muxer are firmware modules of their own: +[`h264enc`](https://github.com/PyDevices/micropython-pydevices/tree/main/modules/h264enc), +[`tsmux`](https://github.com/PyDevices/micropython-pydevices/tree/main/modules/tsmux) +and the PPA's colour conversion, +[`ppa`](https://github.com/PyDevices/micropython-pydevices/tree/main/modules/ppa). +The Companion channel's BrightScript is in +[`tools/roku_companion_app`](https://github.com/PyDevices/pydevices-examples/blob/main/tools/roku_companion_app/README.md), and its +Python side in [`lib/utils/roku_companion.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/utils/roku_companion.py). diff --git a/docs/how-it-works/index.md b/docs/how-it-works/index.md new file mode 100644 index 000000000..dfb55c5ad --- /dev/null +++ b/docs/how-it-works/index.md @@ -0,0 +1,77 @@ +# How it works + +pydevices-examples is the showcase and the browser gallery for the PyDevices +driver stack. It holds runnable applications and helpers. The reusable drivers +and published packages live in +[pydevices](https://github.com/PyDevices/pydevices), and these examples +demonstrate them without publishing them. + +## How an example reaches a screen + +```text +published pydevices product libraries and board_config + | + v +this repository's lib/examples application scripts + | + +-----------+-----------+ + v v +desktop / board runs .site/pyscript gallery +``` + +A script runs on MicroPython, CircuitPython or CPython when a board config and +the packages it imports are present. Editing `lib/` updates the gallery, +because `.site/pyscript/lib` is a symlink to that tree. + +## Application ownership + +Board configs describe hardware. They export neutral pieces such as +`display_drv`, `touch_read` and `host_read`; they do not create an +application-level `appdev.App`. + +Non-LVGL examples opt into the optional `appdev` coordinator: + +```python +import board_config +from board_config import display_drv +import appdev + +app = appdev.App(board_config) +``` + +The app keeps itself alive past the end of the script, so no trailing +`app.run()` is needed. Call it only when you want the script to block at that +point, or need a nonzero exit code. + +LVGL examples use the coordinator bundled with the LVGL binding and do not +import `appdev`: + +```python +from board_config import display_drv +from display_driver import app +``` + +Don't mix `appdev` into an LVGL example unless its architecture needs it. This +separation keeps hardware policy out of board configs and lets an application +bring a different event loop or traffic controller when it wants one. + +## Where to read next + +How [casting to a TV](casting.md) works. The cross-interpreter test matrix and +the PyScript and Playwright debugging notes are in +[`tools/README.md`](https://github.com/PyDevices/pydevices-examples/blob/main/tools/README.md). + +## Related repositories + +- [pydevices](https://github.com/PyDevices/pydevices): the core driver package and the board configs. +- [mip](https://github.com/PyDevices/mip): the PyDevices MIP index. +- [lvgl-bindings](https://github.com/PyDevices/lvgl-bindings): the shared LVGL binding source. + [lvgl-micropython](https://github.com/PyDevices/lvgl-micropython), + [lvgl-circuitpython](https://github.com/PyDevices/lvgl-circuitpython) and + [lvgl-python](https://github.com/PyDevices/lvgl-python) are the interpreter-specific distributions. +- [pygraphics](https://github.com/PyDevices/pygraphics), + [palettes](https://github.com/PyDevices/palettes) and + [pdwidgets](https://github.com/PyDevices/pdwidgets): drawing, colors and widgets. +- [android-runner](https://github.com/PyDevices/android-runner) is the prebuilt Runner that `android.py` stages examples onto. + [android-template](https://github.com/PyDevices/android-template) packages an Android app. +- [pyscript-template](https://github.com/PyDevices/pyscript-template): a standalone PyScript application. diff --git a/docs/images/hello_shapes.png b/docs/images/hello_shapes.png new file mode 100644 index 000000000..a93d45f40 Binary files /dev/null and b/docs/images/hello_shapes.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..58918d61a --- /dev/null +++ b/docs/index.md @@ -0,0 +1,28 @@ +# pydevices-examples + +pydevices-examples is the gallery of runnable programs for OmniPython: the +same scripts draw on a MicroPython-compatible board, a CircuitPython-compatible +board, a desktop window, a browser tab and an Android phone. + +## Get started + +- [Install](get-started/install.md) +- [Your first program](get-started/first-program.md) + +## Guides + +- [Run an example in the browser](guides/run-in-the-browser.md) +- [Cast an app to a TV](guides/cast-to-a-tv.md) +- [Use a Peter Hinch GUI](guides/use-a-peter-hinch-gui.md) +- [Run the TFT examples](guides/run-the-tft-examples.md) + +## Reference + +- [Package names](reference/package-names.md) +- [Cast methods on each runtime](reference/cast-runtimes.md) +- [Screenshots](reference/screenshots.md) + +## How it works + +- [How it works](how-it-works/index.md) +- [How casting works](how-it-works/casting.md) diff --git a/docs/newcomers.md b/docs/newcomers.md deleted file mode 100644 index 0ce95f0d2..000000000 --- a/docs/newcomers.md +++ /dev/null @@ -1,39 +0,0 @@ -# Newcomer's guide to pydevices-examples - -pydevices-examples is the showcase, integration documentation, and browser gallery for the PyDevices driver ecosystem. It contains runnable applications and helpers; reusable drivers and published core packages belong in the sibling [pydevices](https://github.com/PyDevices/pydevices) repository. - -## Start with an example - -The fastest path is the [PyScript gallery](https://PyDevices.github.io/pydevices-examples/pyscript/), which runs the real example scripts in a browser. - -To run the examples from a desktop clone, including the helpers under lib/utils, follow the [README's "Try it" section](../README.md#try-it); the [examples README](../lib/examples/README.md) explains both invocation forms. - -## Mental model - -```text -published pydevices product libraries and board_config - | - v -this repository's lib/examples application scripts - | - +-----------+-----------+ - v v -desktop / board runs .site/pyscript gallery -``` - -Examples demonstrate the portable product stack; they do not publish it. Editing lib/ updates the gallery because .site/pyscript/lib is a symlink to that tree. - -## Repository map - -The [README's repository layout](../README.md#repository-layout) maps the tree. Repository unit tests live in [tests/](../tests/). - -## Application ownership - -Board configurations expose hardware; they do not create an appdev.App. Non-LVGL examples opt into appdev themselves, and LVGL examples use the display_driver coordinator instead. The [README's app ownership section](../README.md#app-ownership) shows both. Don't mix appdev into an LVGL example unless its architecture specifically requires it. - -## Interpreter boundaries - -The same script may run on MicroPython, CircuitPython, or CPython when an appropriate board_config and product stack are present. MicroPython setup uses the PyDevices MIP index and a board package; the [pydevices installation workflows](https://github.com/PyDevices/pydevices/blob/main/docs/install-workflows.md) are authoritative. - -The test matrix is intentionally broader than a quick script run. Read [tools/README.md](../tools/README.md) before cross-interpreter or LVGL testing. For a focused contribution, use the repo virtual environment, run the relevant unit tests, and keep reusable product fixes in pydevices rather than here. - diff --git a/docs/reference/cast-runtimes.md b/docs/reference/cast-runtimes.md new file mode 100644 index 000000000..479ff5ac0 --- /dev/null +++ b/docs/reference/cast-runtimes.md @@ -0,0 +1,30 @@ +# Cast methods on each runtime + +Each method on each runtime that could run it, with real examples through +their `board_config.py`. The numbers are frames a second reaching the screen, +for testris and for the LVGL `lv_test_timer`. + +| Runtime | Companion channel | Miracast to a Roku | Miracast to Windows | HLS | +|---|---|---|---|---| +| `python` (Linux) | 8.2, 9.1 | 30, 30 | 30 | plays | +| `python.exe` (Windows) | 12.2, 10.6 | 30, 30 | 30 | plays | +| `micropython` (unix) | 11.8, 7.4 | no H.264 | no H.264 | no H.264 | +| `micropython.exe` (Windows) | 8.4, 7.7 | no H.264 | no H.264 | no H.264 | +| ESP32-P4 4" panel | 2.2, 2.1 | 29, 18 | 30 | plays | +| ESP32-S3 LCD-7 | 1.8, 1.7 | no H.264 | no H.264 | no H.264 | + +**No H.264** is by design. Desktop MicroPython and the S3 have no H.264 +encoder, and adding one isn't planned, so `CastDisplay` and `HlsDisplay` stop +there with an error that says so. On CPython, ffmpeg does the encoding: put it +on the PATH, or name it in the `FFMPEG` environment variable (Windows +`python.exe` started from WSL needs that). The Miracast stream from CPython is +the same MPEG-TS over RTP that the P4 sends, byte for byte. + +**Miracast to Windows** is testris only, with glitches from Wi-Fi loss (see +[Miracast to a Windows laptop](../guides/cast-to-a-tv.md#miracast-to-a-windows-laptop)). From CPython +it needs a second PC as the receiver: Windows won't take a cast from itself. + +On a desktop, a frame costs a few milliseconds of PNG encoding. On the P4 it's +about 180 ms at 480x270 and on the S3 about 380 ms, which is why the boards get +2 frames a second over the Companion channel and 18 to 29 over Miracast, where the P4's H.264 encoder +does the work. diff --git a/docs/reference/package-names.md b/docs/reference/package-names.md new file mode 100644 index 000000000..886e71748 --- /dev/null +++ b/docs/reference/package-names.md @@ -0,0 +1,14 @@ +# Package names + +Python distributions on TestPyPI use the organization prefix. Imports and MIP +packages are unprefixed. `pydevices` ships the whole core `lib/` tree as one +distribution, and the MIP index publishes it the same way: one `pydevices` +package, not one per component. + +| TestPyPI distribution | Python import / MIP name | +|---|---| +| `pydevices` | `displaydev`, `audiodev`, `appdev`, `events`, `keys`, `multimer`, `boarddev` | +| `pydevices-pygraphics` | `pygraphics` | +| `pydevices-palettes` | `palettes` | +| `pydevices-pdwidgets` | `pdwidgets` | +| `pydevices-lvgl` | `lvgl` | diff --git a/docs/reference/screenshots.md b/docs/reference/screenshots.md new file mode 100644 index 000000000..71e1cdadd --- /dev/null +++ b/docs/reference/screenshots.md @@ -0,0 +1,28 @@ +# Screenshots + +What the examples draw, captured from a desktop run, a phone or an installed PWA. + +| Image | Image | Image | +|-------|-------|-------| +| ![bmp565_simpletest.png](../screenshots/bmp565_simpletest.png) | ![bmp565_sprite.gif](../screenshots/bmp565_sprite.gif) | ![chango.png](../screenshots/chango.png) | +| ![color_test.png](../screenshots/color_test.png) | ![console_advanced_demo.gif](../screenshots/console_advanced_demo.gif) | ![displaybuf_simpletest.png](../screenshots/displaybuf_simpletest.png) | +| ![fonts.png](../screenshots/fonts.png) | ![framebuf_simpletest.png](../screenshots/framebuf_simpletest.png) | ![noto_fonts.png](../screenshots/noto_fonts.png) | +| ![paint.png](../screenshots/paint.png) | ![proverbs.png](../screenshots/proverbs.png) | ![testris.png](../screenshots/testris.png) | +| ![tiny_toasters.gif](../screenshots/tiny_toasters.gif) | ![piano.png](../screenshots/piano.png) | ![google_photos.png](../screenshots/google_photos.png) | +| ![google_photos_view.png](../screenshots/google_photos_view.png) | ![google_photos_connect.png](../screenshots/google_photos_connect.png) | ![spectrum_800x480.gif](../screenshots/spectrum_800x480.gif) | +| ![spectrum_800x480.png](../screenshots/spectrum_800x480.png) | ![spectrum_800x480_segmented.png](../screenshots/spectrum_800x480_segmented.png) | ![spectrum_320x170.png](../screenshots/spectrum_320x170.png) | +| ![car_cluster.png](../screenshots/car_cluster.png) | | | + +On Android (the PyDevices Runner) and as an installed PWA: + +| Image | Image | Image | +|-------|-------|-------| +| ![dogfood-android-piano.png](../screenshots/dogfood-android-piano.png) | ![dogfood-android-drum.png](../screenshots/dogfood-android-drum.png) | ![dogfood-android-drum-panel.png](../screenshots/dogfood-android-drum-panel.png) | +| ![dogfood-pwa-piano.png](../screenshots/dogfood-pwa-piano.png) | ![dogfood-pwa-drum.png](../screenshots/dogfood-pwa-drum.png) | ![dogfood-pwa-drum-panel.png](../screenshots/dogfood-pwa-drum-panel.png) | +| ![calc_lvgl_android.png](../screenshots/calc_lvgl_android.png) | ![paint_android.png](../screenshots/paint_android.png) | | + +The sensor hub and the fleet page ([sensor hub](https://github.com/PyDevices/pydevices-examples/blob/main/lib/examples/sensor_hub/README.md) and [fleet page](https://github.com/PyDevices/pydevices-examples/blob/main/lib/examples/fleet_page/README.md)). The mobile shot is a phone-sized browser on the PC: + +| Image | Image | Image | +|-------|-------|-------| +| ![sensor_hub_dashboard_pc.png](../screenshots/sensor_hub_dashboard_pc.png) | ![sensor_hub_dashboard_mobile.png](../screenshots/sensor_hub_dashboard_mobile.png) | ![fleet_page.png](../screenshots/fleet_page.png) | diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..1cdafe014 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,4 @@ +# Local preview: use the checkout of .github next to this repository, +# pip install -e ../.github/docs-config +# Read the Docs and CI install the tagged release: +pydevices-docs @ git+https://github.com/PyDevices/.github@docs-v0.1.0#subdirectory=docs-config diff --git a/docs/screenshots/README.md b/docs/screenshots/README.md deleted file mode 100644 index 005da30ff..000000000 --- a/docs/screenshots/README.md +++ /dev/null @@ -1,24 +0,0 @@ -| Image | Image | Image | -|-------|-------|-------| -| ![bmp565_simpletest.png](bmp565_simpletest.png) | ![bmp565_sprite.gif](bmp565_sprite.gif) | ![chango.png](chango.png) | -| ![color_test.png](color_test.png) | ![console_advanced_demo.gif](console_advanced_demo.gif) | ![displaybuf_simpletest.png](displaybuf_simpletest.png) | -| ![fonts.png](fonts.png) | ![framebuf_simpletest.png](framebuf_simpletest.png) | ![noto_fonts.png](noto_fonts.png) | -| ![paint.png](paint.png) | ![proverbs.png](proverbs.png) | ![testris.png](testris.png) | -| ![tiny_toasters.gif](tiny_toasters.gif) | ![piano.png](piano.png) | ![google_photos.png](google_photos.png) | -| ![google_photos_view.png](google_photos_view.png) | ![google_photos_connect.png](google_photos_connect.png) | ![spectrum_800x480.gif](spectrum_800x480.gif) | -| ![spectrum_800x480.png](spectrum_800x480.png) | ![spectrum_800x480_segmented.png](spectrum_800x480_segmented.png) | ![spectrum_320x170.png](spectrum_320x170.png) | -| ![car_cluster.png](car_cluster.png) | | | - -On Android (the PyDevices Runner) and as an installed PWA: - -| Image | Image | Image | -|-------|-------|-------| -| ![dogfood-android-piano.png](dogfood-android-piano.png) | ![dogfood-android-drum.png](dogfood-android-drum.png) | ![dogfood-android-drum-panel.png](dogfood-android-drum-panel.png) | -| ![dogfood-pwa-piano.png](dogfood-pwa-piano.png) | ![dogfood-pwa-drum.png](dogfood-pwa-drum.png) | ![dogfood-pwa-drum-panel.png](dogfood-pwa-drum-panel.png) | -| ![calc_lvgl_android.png](calc_lvgl_android.png) | ![paint_android.png](paint_android.png) | | - -Device scenarios, Batch 0 ([sensor hub](../../lib/examples/sensor_hub/README.md) and [fleet page](../../lib/examples/fleet_page/README.md)). The mobile shot is a phone-sized browser on the PC: - -| Image | Image | Image | -|-------|-------|-------| -| ![sensor_hub_dashboard_pc.png](sensor_hub_dashboard_pc.png) | ![sensor_hub_dashboard_mobile.png](sensor_hub_dashboard_mobile.png) | ![fleet_page.png](fleet_page.png) | diff --git a/docs/tft-gui.md b/docs/tft-gui.md deleted file mode 100644 index daf465d8a..000000000 --- a/docs/tft-gui.md +++ /dev/null @@ -1,20 +0,0 @@ -# TFT examples (russhughes) - -Many pydevices-examples applications were ported from [@russhughes st7789py_mpy](https://github.com/russhughes/st7789py_mpy). - -## Config - -Use or adapt `tft_config.py` from [`lib/utils/tft_config.py`](https://github.com/PyDevices/pydevices-examples/blob/main/lib/utils/tft_config.py). Some example filenames were search-replaced during port — if an example fails to import config, compare with that file. - -## Font and image tools - -- [text_font_converter.py](https://github.com/russhughes/st7789py_mpy/blob/master/utils/text_font_converter.py) → `tft_text.text()` -- [write_font_converter.py](https://github.com/russhughes/st7789py_mpy/blob/master/utils/write_font_converter.py) → `tft_write.write()` -- [image_converter.py](https://github.com/russhughes/st7789py_mpy/blob/master/utils/image_converter.py) → `.bitmap()` - -See the pygraphics [graphics guide](https://github.com/PyDevices/pygraphics/blob/main/docs/graphics-guide.md) -and [graphics files](https://github.com/PyDevices/pygraphics/blob/main/docs/graphics-files.md). - -## Notable examples - -`tiny_hello.py`, `feathers.py`, `proverbs/`, `chango/`, `tiny_toasters/` (game port). diff --git a/lib/examples/cast/README.md b/lib/examples/cast/README.md index 211d7f4ab..c72dfc5a7 100644 --- a/lib/examples/cast/README.md +++ b/lib/examples/cast/README.md @@ -17,11 +17,11 @@ the room. Over Wi-Fi, it can: Every way the examples cast, to a Roku with or without our channel, to Windows and to VLC, with the boards and interpreters for each, is in the -[casting guide](../../../docs/casting.md). +[casting guide](../../../docs/guides/cast-to-a-tv.md). It needs P4 firmware that carries `castif`, `h264enc` and `tsmux`, which any micropython-pydevices build with `--modules all` (or one naming all three) does; the command for each -P4 is in its [newcomer's guide](https://github.com/PyDevices/micropython-pydevices/blob/main/docs/newcomers.md#boards-we-build-for). +P4 is in its [boards list](https://github.com/PyDevices/micropython-pydevices/blob/main/docs/reference/boards.md#boards-we-build-for). Anything else lacks the H.264 encoder. ## Try it diff --git a/lib/utils/README.md b/lib/utils/README.md index 0670c4c27..122c046fd 100644 --- a/lib/utils/README.md +++ b/lib/utils/README.md @@ -37,11 +37,11 @@ instead, and are installed by `pydevices-desktop`. | [`lv_encoder_emu.py`](lv_encoder_emu.py) | Desktop-only soft encoder on a secondary surface, standing in for MCU `machine.Encoder` / `rotaryio` (see [`examples/lv_multi_display.py`](../examples/lv_multi_display.py)) | | [`tft_text.py`](tft_text.py), [`tft_write.py`](tft_write.py) | @russhughes font rendering | | [`png.py`](png.py) | Experimental PNG support | -| [`color_setup.py`](color_setup.py), [`hardware_setup.py`](hardware_setup.py), [`touch_setup.py`](touch_setup.py), [`fetch_ph_gui.py`](fetch_ph_gui.py) | [Peter Hinch GUI](../../docs/peterhinch-guis.md) integration | +| [`color_setup.py`](color_setup.py), [`hardware_setup.py`](hardware_setup.py), [`touch_setup.py`](touch_setup.py), [`fetch_ph_gui.py`](fetch_ph_gui.py) | [Peter Hinch GUI](../../docs/guides/use-a-peter-hinch-gui.md) integration | `gui/` is an upstream Peter Hinch tree fetched on demand and gitignored — one of nano / micro / touch at a time. See -[peterhinch-guis.md](../../docs/peterhinch-guis.md). +[use-a-peter-hinch-gui.md](../../docs/guides/use-a-peter-hinch-gui.md). `pdwidgets/` and `palettes/` moved out to their own repositories: [pdwidgets](https://github.com/PyDevices/pdwidgets), diff --git a/lib/utils/roku_companion.py b/lib/utils/roku_companion.py index d564d1d23..95675cf8e 100644 --- a/lib/utils/roku_companion.py +++ b/lib/utils/roku_companion.py @@ -494,7 +494,7 @@ def video(self, url, format="hls"): Any stream a Roku ``Video`` node plays works (``format`` is its ``streamFormat``: "hls", "mp4", "dash"...). For HLS, audio must be AAC; LPCM plays silent. A live stream runs about 7-10 s behind with - 1-second segments and a 3-segment playlist (docs/casting.md, "HLS + 1-second segments and a 3-segment playlist (docs/guides/cast-to-a-tv.md, "HLS video to a Roku or VLC"). Example:: diff --git a/tools/roku_companion_app/README.md b/tools/roku_companion_app/README.md index 9bb07b675..addddbc2f 100644 --- a/tools/roku_companion_app/README.md +++ b/tools/roku_companion_app/README.md @@ -15,7 +15,7 @@ apps**. Then run any example in [`lib/examples/roku/`](../../lib/examples/roku/) its argument, or with `ROKU_IP` set. What else can put a picture on the TV, with nothing installed on it, is in the -[casting guide](../../docs/casting.md). +[casting guide](../../docs/guides/cast-to-a-tv.md). Updates reach the running channel as ECP `input` events, so the screen doesn't restart for each one; the library launches the channel only when it isn't the