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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ Run `uv run paperpi render --help` for all options. How to write a plugin: [docs

### A config file

`paperpi example-config` prints an example config file that works as it is: a virtual screen, a clock, and the weather in Berlin and Rio. Every other setting is a comment with its default and a short help text. [paperpi.example.toml](paperpi.example.toml) is the same text. Before you use the weather blocks, put your own email address in `email` (met.no asks for it). `-o` doesn't replace a file that is already there, unless you add `--force`.
`paperpi example-config` prints an example config file that loads without errors: a virtual screen, a clock, and weather blocks for Berlin and Rio. Every other setting is a comment with its default and a short help text. [paperpi.example.toml](paperpi.example.toml) is the same text. The weather blocks are not shown until you remove the `#` in front of `email` and fill in your own, real email address: met.no's terms of service ask for it, so the example leaves it empty. Until then `paperpi list` shows them as "needs email". `-o` doesn't replace a file that is already there, unless you add `--force`.

```bash
uv run paperpi example-config -o paperpi.toml
uv run paperpi list --config paperpi.toml
```

`paperpi list` shows the plugins of a config file, one line each, in the order of the file: name, type, on or off, level, display time, refresh, layout and storage (the ones used: the setting, or else the plugin's suggestion or first layout; storage is the size limit and the age limit, for example `500 MB, 30 d`, or `500 MB, no age limit`). Anything wrong with the file is shown first; a block with an error is left out of the list.
`paperpi list` shows the plugins of a config file, one line each, in the order of the file: name, type, on (`yes`, `no`, or `needs <settings>` when a required setting is missing), level, display time, refresh, layout and storage (the ones used: the setting, or else the plugin's suggestion or first layout; storage is the size limit and the age limit, for example `500 MB, 30 d`, or `500 MB, no age limit`). Anything wrong with the file is shown first; a block with an error is left out of the list.

```
name type on level display refresh layout
Expand Down Expand Up @@ -124,7 +124,7 @@ vcom = -1.90

Then start `paperpi run` as above, as a user in the `spi` and `gpio` groups (the first user on Raspberry Pi OS already is; otherwise `sudo usermod -aG spi,gpio $USER`, then log in again). A new image from the plugin already on screen is a fast refresh of only the changed area. Another plugin gets a full refresh, and so does every 5th fast refresh in a row (`max_refresh = 4`). If the screen does not answer at start, the error is in the log and PaperPi keeps running and tries again.

At start, PaperPi first shows its name, version and the address of its web interface (with a QR code to open it on a phone) for a minute, while the plugins get their first pictures. `splash_time` in `[display]` sets how long, in seconds (`0`: not at all). With no plugin switched on, for example right after installing, it stays on screen until one is, so the address to set PaperPi up is always there.
At start, PaperPi first shows its name, version and the address of its web interface (with a QR code to open it on a phone) for a minute, while the plugins get their first pictures. `splash_time` in `[display]` sets how long, in seconds (`0`: not at all). With no plugin ready to show, for example right after installing, it stays on screen until one is, so the address to set PaperPi up is always there.

When no plugin has anything to show (e.g. no music is playing), a small clock is shown at the bottom of the screen, so you can tell the screen still works. `fallback_clock = false` in `[display]` switches it off, which is not recommended.

Expand Down
3 changes: 3 additions & 0 deletions docs/decisions/config-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ display_time = 255

The file only holds settings that differ from the default. This keeps it short and easy to fix by hand. When a later version improves a default, you get it automatically. The web interface shows every setting with its default filled in. `paperpi.example.toml` is generated from the program (`paperpi example-config`), so it is always up to date; a test checks the copy in the repository. It is short and works as it is: `[display]` with a virtual screen, a clock, and the weather in Berlin and in Rio (the same plugin type twice). Every setting it doesn't set is a comment with its default, and its help text on the line above. The first `[[plugin]]` block lists all the settings every plugin has; the other blocks list only `refresh` and `layout`, because their defaults depend on the plugin. `storage_mb` and `storage_days` can also differ per plugin, but most plugins keep the defaults, so they are only in the first block; `paperpi list` shows the values used (M4 issue #224). The copy in the repository is not edited by hand: it is made again with `paperpi example-config -o paperpi.example.toml --force`. Agreed with txoof on 2026-10-06: the example does not list every plugin. Adding and removing plugin blocks is the job of a config manager (the web interface, M5), which builds a block the same way (`paperpi.example.plugin_block`). That function refuses unknown settings and values the plugin would not accept, and checks that PaperPi reads the block back as written. Plugin names may not hold control characters (such as tab, new line or the terminal's ESC), because they can't be written back to the file reliably.

*Update 2026-10-07 (M5 part 2a, issue #238):* the example no longer fills in a made-up email address, because met.no's terms of service ask for a real one. It loads without errors, but the weather blocks are not shown, and the check warns, until you fill in `email`.

### Checking the file

Each part of the config (display, web, each plugin type) is described in code as a list of settings, each with a type, a default and a short help text. The `pydantic` package does this. From the same description we get:
Expand All @@ -82,6 +84,7 @@ What happens when something is wrong:
| File can't be read at all, or the `display` / `web` part is wrong | Runs on the **last good** copy, a copy that is saved each time the config loads correctly. A warning on the screen and in the web interface names the wrong line. |
| ...and there is no last good copy (first install) | Shows an error screen with a QR code (a square barcode a phone camera can scan) that opens the web interface. |
| One plugin block is wrong (missing value, text where a number belongs) | Only that plugin is switched off. Everything else runs. The web interface shows which setting is wrong. |
| A switched-on plugin is missing a required setting (e.g. `email`, see `plugin-interface.md`) | The plugin is not shown and not counted as broken. A warning names the missing settings; `paperpi list` shows "needs …". |
| Unknown setting name, e.g. `lattitude` | Warning only: "unknown setting, did you mean `latitude`?" |

The web interface only accepts valid values, so most of these mistakes can only happen through hand edits.
Expand Down
3 changes: 2 additions & 1 deletion docs/decisions/live-config-reload.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ What is on screen only lasts until the next cycle anyway, so this is kept simple
- A changed plugin that is on screen is updated and redrawn right away, so the user sees the result.
- A plugin that is removed or switched off while on screen: rotation moves on to the next plugin.
- A new plugin joins the end of the rotation.
- *(M5 part 2a)* A plugin that is missing a required setting is treated as switched off: filling the setting in and reloading starts it (at its place in the file), and emptying it takes the plugin out of the rotation.

**Update 2026-10-05 (M4, issue #205):** plugins take turns in the order of the config file, so a new plugin takes the place where it is in the file (the end, when it is added at the end). Until reloading screen settings is built (M4 issue #222, part 5b), changed screen settings take effect at the next start, with a warning in the log. *(Built in part 5b; see the table below.)*

Expand All @@ -48,7 +49,7 @@ What is on screen only lasts until the next cycle anyway, so this is kept simple
| `max_refresh` (fast refreshes before a full one), `vcom` | At once. The screen helper process (see `display-driver-interface.md`) starts again with the new values; the next write is full. |
| Rotation, colour on/off | At once. Every plugin draws again at its new size; the screen keeps its picture until the new images are ready. Colour is only offered for screens that can show colour. There is no mirror setting (yet). |
| Cleaning interval (`clean_every`), what happens on exit (`on_exit`), the fallback clock | At once. |
| `splash_time` | Only matters at start: a change during the start splash sets when it ends; a reload never shows the splash again. (With no plugin switched on the splash is shown whatever it says.) |
| `splash_time` | Only matters at start: a change during the start splash sets when it ends; a reload never shows the splash again. (With no plugin ready to show the splash is shown whatever it says.) |
| `type`, `model` (virtual screen: `width`, `height`, `mode`) | At the next start of PaperPi. |

*Update (M5 part 1, issue #238):* `[web]` settings. `login` and `password_hash` apply at a reload (so `paperpi reset-password` followed by a reload works without a restart); `enabled`, `address` and `port` apply at the next start, and a reload says so in the log.
Expand Down
2 changes: 2 additions & 0 deletions docs/decisions/plugin-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ The plugin never talks to the screen. Only the scheduler does.

For sample images and tests, `fetch` is skipped and the sample data goes straight to `draw`. How to write a plugin: `docs/writing-plugins.md`.

**Update 2026-10-07 (M5 part 2a, issue #238, agreed with txoof):** a plugin can mark a setting as **required** with `setting(..., required=True)`, for settings it can't work without, such as an API key. The first ones are `lat`, `lon` and `email` of `met_no` and `moon_phase`: met.no's terms of service ask for a real email address, and a user should not start these plugins without giving one. A plugin that is missing a required setting can be in the config file and switched on, but it is **not shown and not counted as broken**: the config check gives a warning naming the missing settings, `paperpi list` shows "needs email" in its "on" column, and the web interface (part 2b) shows "Needs settings" in place of the on switch. The example config no longer fills in a made-up email address. `setting` is the one standard place for a setting's own options; the input helpers of `web-interface.md` (such as `location`, which looks up latitude and longitude; part 3c) will be options of `setting` too.

### How a plugin is run

- Every update runs in a **new, short-lived process**. When the update is done, the process exits and its memory is given back. A plugin that hangs or crashes is stopped without affecting PaperPi or the other plugins.
Expand Down
6 changes: 3 additions & 3 deletions docs/decisions/plugin-scheduling.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ To avoid it, one single part of PaperPi (the scheduler) decides what is shown an

- A plugin that fails (crash, time limit reached, data source down) is skipped. The rotation moves to the next plugin, and the failed one is tried again at its next refresh.
- After 3 failures in a row, the plugin is left out for 30 minutes and the web interface shows a warning. If its first update after that fails too, it is left out for another 30 minutes at once; one good update ends this.
- If nothing else can be shown because plugins are failing, or no plugin is switched on and the splash screen fails (see "First start" below), the `default` plugin is shown. The scheduler tells it how many plugins are failing, and it shows e.g. "3 of 4 plugins are not working. See the web interface for more information." with a QR code (a square barcode a phone camera can scan) that opens the web interface.
- If nothing else can be shown because plugins are failing, or no plugin is ready to show and the splash screen fails (see "First start" below), the `default` plugin is shown. The scheduler tells it how many plugins are failing, and it shows e.g. "3 of 4 plugins are not working. See the web interface for more information." with a QR code (a square barcode a phone camera can scan) that opens the web interface.
- `default` and `splash_screen` are normal plugins. The scheduler passes the failure status to `default`, starts it only when it is needed, and never puts it in the rotation.

The numbers 3 and 30 minutes are defaults. Time limits and watchdog rules are decided in the error-handling note (#190).
Expand All @@ -91,12 +91,12 @@ Added 2026-10-05 (M4, issue #205), agreed with txoof. Code: `src/paperpi/schedul
- **Taking turns:** several alerts, or several interrupts, take turns for their `display_time` each.
- **Alert settings per plugin:** `alert_reminder` and `alert_max_time` are in each `[[plugin]]` block, like `display_time`. They only matter for level `alert`.
- **Failures:** a failed update is tried again at the plugin's next refresh. One good update sets the count of failures back to 0. A plugin that has nothing to show ("nothing") is skipped, which is not a failure.
- **`default`:** shown when nothing else can be shown and at least one plugin is failing, or when no plugin is switched on and the splash screen fails. If `default` itself fails, the screen also keeps its picture, and the error goes to the log. The QR code comes with the web interface (M5). PaperPi always has a `default` plugin, also when the config has no block for it.
- **`default`:** shown when nothing else can be shown and at least one plugin is failing, or when no plugin is ready to show and the splash screen fails. If `default` itself fails, the screen also keeps its picture, and the error goes to the log. The QR code comes with the web interface (M5). PaperPi always has a `default` plugin, also when the config has no block for it.
- **Fallback clock:** when no plugin has anything to show and none is failing (e.g. only a music plugin, and no music, or an alert has just ended), a small clock is shown: `basic_clock` with its `small` layout, time and date on one line at the bottom, updated every minute. An empty or unchanging screen can't be told apart from a broken one; a clock that changes every minute shows that PaperPi works. It waits until every plugin has reported once, so it doesn't flash up at start, and it never shows an out-of-date time. It can be switched off with `fallback_clock = false` in `[display]`, which is strongly discouraged: the config check shows a hint, and the screen then keeps its last picture. Agreed with txoof 2026-10-05, after the code review.
- **On the minute:** the update starts 1 second after the minute changes. This is the only place where the wall-clock time (the time of day, which can jump when it is corrected) is used; every duration uses the monotonic clock (a clock that only counts forward).
- **Start:** the screen is not touched until the first image is ready.
- **Splash screen at start:** the `splash_screen` plugin is updated first and shown for `[display] splash_time` seconds (default 60; `0`: not at all; v1 had `splash = True`) with PaperPi's name, version, web address and a QR code, while the other plugins update in the background; then the normal choice starts. Alerts and interrupts wait until it ends. If its update fails, the normal choice starts at once. A config reload does not show it again. It is a normal plugin, so start-up needs no drawing code of its own.
- **First start (no plugin switched on):** the first start after installing has only the default config, with no plugins; the user sets PaperPi up in the web interface. Whenever no plugin is switched on, the splash screen stays on screen (whatever `splash_time` says), with a new picture every hour, until a config reload switches one on. If it fails, `default` says that no plugin is switched on. So the default config written by the installer (M6) has no `[[plugin]]` blocks. Agreed with txoof 2026-10-07.
- **First start (no plugin ready to show):** the first start after installing has only the default config, with no plugins; the user sets PaperPi up in the web interface. Whenever no plugin is ready to show, the splash screen stays on screen (whatever `splash_time` says), with a new picture every hour, until a config reload switches one on. If it fails, `default` says that no plugin is ready to show. So the default config written by the installer (M6) has no `[[plugin]]` blocks. Agreed with txoof 2026-10-07.
- **Config reload** (see `live-config-reload.md`): on the reload signal the config file is read again. Plugins whose settings did not change keep their place and image. A changed plugin keeps its old image on screen until its new one is ready. A broken file is not applied. Which screen settings apply at once and which at the next start: see the table in `live-config-reload.md` (built in M4 part 5b). Plugins take turns in the order of the new file. When the plugin on screen is removed, the rotation goes on with the one after it.
- **Stopping:** updates that have not started are dropped, and running ones are stopped at once, so stopping never waits for a hanging plugin's time limit.
- **Screen write fails:** it is tried again at the next update of the plugin on screen; when the screen watchdog pauses writes (`errors-and-time-limits.md`), at the end of the pause. The error is logged once, and a line is logged when writes work again.
Expand Down
11 changes: 10 additions & 1 deletion docs/writing-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ Each setting is a field of the plugin's `Settings` class, with a type, a default
- Every setting needs a default.
- Don't use the names of the shared settings, which every `[[plugin]]` block already has: `name`, `type`, `enabled`, `level`, `display_time`, `refresh`, `time_limit`, `layout`, `alert_reminder`, `alert_max_time`.
- Use `pydantic.SecretStr` as the type for API keys and passwords. PaperPi then never shows their values in error messages or logs.
- A setting the user must fill in before the plugin can work (a place, an email address, an API key) is made with `paperpi.plugin.setting(..., required=True)` instead of `Field`. It takes the same arguments as `Field`. Its default must be "not set": `None`, empty text or an empty `SecretStr` (text of only spaces also counts as not set). Until every required setting is filled in, the plugin is not shown and not counted as broken; the config check and `paperpi list` (and from M5 part 2b the web interface) say which settings are missing. The example block marks them "(required)". `setting` is also where later options for a single setting are added, such as a web interface helper that looks up latitude and longitude (M5 part 3c).

```python
from paperpi.plugin import PluginSettings, setting


class Settings(PluginSettings):
lat: float | None = setting(None, required=True, ge=-90, le=90, description="Latitude")
```

In the config file, the plugin's settings go in its `[[plugin]]` block:

Expand Down Expand Up @@ -133,7 +142,7 @@ data = answer.json()
```

`get` takes:
- `contact`: an email or web address, added to the User-Agent header (the line in every request that names the program), so the service knows whom to ask about problems. Some services, like met.no, require it. Give your plugin a setting for it, as `met_no` does with `email`.
- `contact`: an email or web address, added to the User-Agent header (the line in every request that names the program), so the service knows whom to ask about problems. Some services, like met.no, require it. Give your plugin a setting for it, as `met_no` does with `email`, and make it required with `setting(..., required=True)`.
- `headers=`: more headers to send, for example an API key. They are sent only to the server of `url`: if that server redirects to another one, they are left out.
- `if_modified_since=`: the `last_modified` of an earlier answer. If nothing changed since, `answer.not_modified` is `True` and `answer.body` is empty. Save `last_modified` (it can be `None` when the server didn't send one) and the data in `context.storage`, so the next update can ask.
- `max_bytes=`: a higher size limit, for example for large images.
Expand Down
Loading
Loading