Change a screen's settings on the screen itself (0.2.52)

Holding the top bar for about a second and a half opens a settings page on the
screen, with a blue line filling along the top edge while you hold. A built-in
screen.settings tile and the esphome.<screen>_open_settings action open it too.

The page is one constexpr table of rows in settings_screen.h: Brightness,
Night, Screen (clock, back to page 1, swiping, rotation) and This screen (name,
address, firmware, Home Assistant, Restart). Toggles flip on a tap, numbers and
times step with -/+ (a held time walks whole hours), a choice cycles in a chip,
and a row that depends on a switch above it is greyed out. The LVGL objects exist
only while the page is open: +608 bytes of RAM and +10.7 KB of flash on the CYD.
A change is saved, applied and reported through the existing screen_setting
event, so ESP Screens shows the same value within a second.

New setting auto_home (default on, 2 minutes): an open card, the settings page
or a later page goes back to page 1 after that long without a touch, not only on
standby. It travels beside the frozen eleven-key settings block with its own
preference, so older firmware and older apps keep working.

docs/SETTINGS.md is the recipe for adding a setting; the Claude skill explains
the page. Firmware 0.2.44.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-16 10:05:53 +02:00
parent 93c789dc48
commit b83164d61a
28 changed files with 1645 additions and 27 deletions
+38
View File
@@ -176,6 +176,44 @@ icons sit off-center in the browser); run `generate_packages.py` afterward.
The `MDI_GLYPH_*` substitutions are retired: `TILEn_ICON` in manual
profiles must come from the set. No changed preferences or keys.
### Compatibility 0.2.52 / firmware 0.2.44
App and firmware. Storage version and tile protocol stay 1; everything is additive, so an old screen
with this app, and a new screen with an older app, both keep working.
- **Two settings, beside the frozen block.** `settings.auto_home` (bool, default `true`) and
`settings.auto_home_seconds` (30-3600, default 120) are new keys in `SETTING_RULES`, stored in
storage version 1. On the wire they travel as their own top-level keys of the layout message, next to
`swipe_pages` and `rotation`; the eleven-field `settings` object is unchanged, because firmware before
0.2.44 refuses any other size. `core.SETTINGS_BESIDE_BLOCK` is the one list of such keys and
`layout_message` uses it. Older firmware ignores the two keys.
- **Firmware storage.** A new preference key `0x484F4D31` holds `HomeTimeout` (two uint32: enabled,
seconds). The Settings structure `0x53435231`, swipe `0x53575031`, rotation `0x524F5431` and the CYD
calibration are untouched. `runtime_tiles::persist_settings()` saves all of them; ESPHome compares each
blob with NVS before writing, so unchanged records cost no flash write.
- **Settings page on the screen.** `components/smart_display/settings_screen.h`: one `constexpr` table of
pages and rows, drawn into a root object created on open and deleted on close. Opened by holding the
top bar (`attach_hold`, substitutions `SETTINGS_HOLD_X/W/DRIFT_PX`; on the Guition the strip keeps
clear of both swipe bands), by the `screen.settings` tile, or by the API action `open_settings`
(`page` 0-4, negative closes and goes home). A change is saved, applied (`apply_screen_settings`) and
reported with the existing `esphome.screen_setting` event; the manager's handler accepts every key in
`SETTING_RULES`, the new ones included. Row taps use the board's own drift limit
(`TOUCH_MOVE_LIMIT_PX`), the hold its own.
- **Back to page 1 by itself.** The 1 s interval runs the new `go_home` script (settings page closed,
cards closed, page 1) when `auto_home` is on and nothing touched the screen for `auto_home_seconds`,
while a card, the settings page or a later page is showing. Standby now always closes the settings page;
an alert closes it too.
- **`screen.settings` tile.** A second built-in entity next to `screen.clock`. `min_firmware` asks for
0.2.44, so the manager does not send a layout with it to older firmware. `Tile::is_clock()` now marks
the clock-only paths (minute redraw, second hand) that used `builtin()`; the editor mockup takes the
icon from `tile_icons.BUILTIN_TILES`.
- **Glyphs.** `monitor`, `information-outline` and `restart` join `tile_icons.FIXED` (183 glyphs).
- **API.** `open_settings` is a new ESPHome action; Home Assistant lists it as
`esphome.<screen>_open_settings` after the device reconnects with 0.2.44. The `IP address` text sensor
gained `id: wifi_ip`; its entity is unchanged.
- **Docs.** docs/SETTINGS.md is the recipe for adding a setting; the Claude skill gains a section on the
page (one more YAML example), so an installed skill shows as outdated until it is installed again.
### Compatibility 0.2.51 / firmware 0.2.43
App only; no firmware change, no protocol change, no storage change. Layouts keep their shape, so an
+98
View File
@@ -0,0 +1,98 @@
# Settings: on the screen and in ESP Screens
Every screen setting lives in two places at once: the **Screen settings** block of the add-on editor, and
the **settings page on the screen itself** (firmware 0.2.44+). This page says what that page is made of,
and exactly what adding one more setting takes.
## What the user sees
Holding the top bar of the overview for about one and a half seconds opens the page; a blue line grows
along the top edge while you hold, and letting go before it finishes cancels. A screen can also carry a
`screen.settings` tile, and Home Assistant can open it with `esphome.<screen>_open_settings`.
The page is a menu of groups, each of which opens a page of its own:
| Group | Rows |
|---|---|
| Brightness | Brightness, Auto standby, Standby after, Standby brightness |
| Night | Night mode, Starts, Ends, Night brightness |
| Screen | Clock, Back to page 1, After, Also on standby, Swipe between pages, Rotation (boards that turn) |
| This screen | Screen, Address, Firmware, Home Assistant, Restart |
Every change is saved on the screen, applied at once, and sent to the add-on as an
`esphome.screen_setting` event, so the editor shows the same value within a second. The add-on remains
the owner: its next layout message repeats every setting, and a screen that was offline catches up then.
## The rules the page follows
- **Two levels, never three.** A group page is the deepest place a setting can live.
- **No free scrolling.** A group that does not fit gets the same `< Previous / Next >` pager as the tile
pages. A 320x240 board shows five rows, a 480x480 board six.
- **A row is a control, not a form.** Toggles flip on tap, numbers and times have `-` and `+`, a choice
cycles through its options in a chip on the right.
- **A row that depends on a switch above it is greyed out, not hidden**, so the page never jumps around.
- **Only settings that belong to this piece of glass.** Tiles, the top bar and pages belong to the editor,
which has a mouse and a keyboard; the screen gets what you want to change while standing in front of it.
## Adding a setting
The example below adds "Beep on touch" (`beep`), a switch.
### 1. Where the value lives
Two homes, and the choice is fixed by history:
- `screen_settings::Settings` in `components/smart_display/screen_settings.h` is the **frozen** block of
eleven values that ESP Screens sends as `settings`. Its format is version 1 and changing it needs a
preference migration *and* firmware-version gating in the add-on, because older firmware refuses a
`settings` object that does not have exactly its own keys. Don't.
- Anything new is a plain value in `settings_screen.h` next to `swipe_pages`, `rotation` and `auto_home`,
with its own preference and its own key in the layout message. That is backward and forward
compatible in both directions: old firmware ignores the key, a new screen with an old add-on keeps its
saved value.
```cpp
// settings_screen.h, next to the others
inline int32_t swipe_pages = 0, rotation = 0, auto_home = 1, auto_home_seconds = 120, beep = 0;
```
Storing it (`components/smart_display/runtime_tiles.h`): extend `HomeTimeout`-style records or add one of
your own, load it in `load_settings()`, write it in `persist_settings()`, and accept the key in
`receive()` next to `auto_home` — validate first, then save.
### 2. The row
One line in the table of the group it belongs to, in `settings_screen.h`:
```cpp
toggle("Beep on touch", [] { return beep; },
[](int32_t value) { beep = value ? 1 : 0; changed("beep", beep); }),
```
`changed(key, value)` saves, applies and reports in one go; `key` must be the add-on's key exactly.
The row kinds are `toggle`, `number` (fixed step, optional unit), `duration` (seconds, steps grow with
the value), `moment` (minutes since midnight, quarters by tap and hours while held), `choice`, `info`
and `action`. A row can carry `shown` (leave it out on boards that lack the hardware) and `enabled`
(grey while the switch it depends on is off).
Watch the count: a group of more than five rows gets a pager on a 320x240 board. Six is the maximum
that still fits a 480x480 board in one go.
### 3. The add-on
- `screen_manager/app/core.py`: one line in `SETTING_RULES` (`'beep': (False, None, None)`), which gives
you validation, storage and the write-back from the screen for free.
- `screen_manager/app/server.py`: add the key to `extra` in `layout_message` and send it as its own key,
next to `auto_home`. Never let it into the `settings` block.
- `screen_manager/app/static/app.js`: one line in `settingDefinitions` for the editor's own control.
- Needs new firmware? `min_firmware()` in `core.py` keeps the screen from being told about something it
cannot do.
### 4. Tests and proof
- `tests/test_settings_screen.cpp` (`clang++ -std=c++17 -Wall -Wextra -Werror -I.`) walks every page and
every row: a new row is checked by the table test as soon as it exists.
- `tests/test_settings_view.py` and `tests/test_layout.py` cover the add-on side.
- Render it before believing it: `.esphome/readme-render/host_build.py <board> --compile` builds the real
firmware for the Mac and `render_settings.py <board>` drives it over the API and saves PNGs of every
page, including the hold gesture, the time picker and the settings tile.
+70
View File
@@ -0,0 +1,70 @@
# Test results app 0.2.52 / firmware 0.2.44 (2026-09-16)
The settings page on the screen itself, the `screen.settings` card, and "back to page 1 by itself".
Firmware and app both change; the protocol grows two optional keys and the stored layouts gain two
settings with defaults, so an old screen with the new app and a new screen with an old app both keep
working. See CHANGELOG 0.2.52 and docs/SETTINGS.md.
## Automated
- Python (`.venv-portal`): 200 tests OK. New `tests/test_settings_page.py` (7 tests): every key the
firmware reports back (`changed("...")` in settings_screen.h) is a key the add-on validates; the
`settings` block the screen receives still holds exactly its eleven frozen keys while `auto_home`
and `auto_home_seconds` ride beside it; the defaults and the refusals of the two new settings;
`screen.settings` as a built-in tile that asks for firmware 0.2.44; and both board profiles carrying
the page, the hold strip (clear of the Guition's swipe bands), the `open_settings` action and the
`go_home` script.
- C++ (`clang++ -std=c++17 -Wall -Wextra -Werror -I.`): 14/14 PASS, including new
`tests/test_settings_screen.cpp`: the duration ladder and its symmetry, a number stopping at its
ends, a time wrapping around midnight and walking whole hours while a key is held, every text
("45 sec", "1 h 30", "22:00", "10:00 PM"), the rows reading and writing the real settings (including
a lower Brightness pulling the two dim levels down), a row hidden on a board that cannot turn, the
table itself (every menu row opens a real page, every control has a reader and a writer), how many
rows fit per board and when the pager appears, and that one change stores, applies and reports once.
- `generate_packages.py --check` green, `generate_icons.py --check` green (183 glyphs: three new).
- `esphome config` green for both board profiles.
## Builds (ESPHome 2026.6.2)
Full board profiles compiled for the ESP32, not just the host:
| Profile | RAM | Flash |
|---|---|---|
| home-like-2432s028.yaml (CYD) | 23.0% (75,492 B) | 80.6% (1,479,167 B of 1,835,008) |
| guition-4848s040.yaml | 24.0% (78,732 B) | 19.4% (1,576,143 B of 8,126,464) |
| easy-cyd-device.yaml (package, local components) | 23.0% (75,276 B) | 76.0% (1,395,143 B) |
| easy-guition-device.yaml (package, local components) | 24.0% (78,508 B) | 18.4% (1,494,075 B) |
The two Easy profiles were compiled one after the other, as RELEASING asks, from packages generated
out of the board profiles above.
What the page costs, measured by building the same CYD profile at 0.2.51 (firmware 0.2.43) in a clean
worktree: **+608 bytes of RAM** (74,884 -> 75,492) and **+10.7 KB of flash** (1,468,191 -> 1,479,167).
The rows are a `constexpr` table in flash and the LVGL objects are built when the page opens and
deleted when it closes, like the cards of a tile, so a closed page costs only that handful of bytes.
The ESP32 build also caught what the Mac could not: on xtensa `int32_t` is `long`, so a row reader
that returns a plain `int` does not convert to `Read` at all. Every reader now says `-> int32_t`.
## Rendered on the real firmware (host build, SDL)
`.esphome/readme-render/host_build.py <board> --compile` builds the actual firmware for the Mac and
`render_settings.py <board>` drives it over its own API. Both boards were rendered and looked at:
- Every page on both boards: the menu, Brightness, Night, Screen, This screen.
- Holding the top bar: the blue line is halfway after 0.9 s of a 1.5 s hold and disappears on release.
- The time picker worked like a finger: four taps on `+` moved 22:00 to 23:00 (quarters), then holding
walked whole hours to 03:00. Switching the clock to 12 hour turned the same times into 3:00 AM and
7:00 AM.
- A switch row turning its dependent rows live and grey again (Night mode, Auto standby).
- The `screen.settings` card on the overview, and a tap on it opening the menu.
- "Back to page 1 by itself": with the last touch aged past the timeout, the open page closed and the
overview came back on page 1.
## Not tested / for the owner
- Hardware. Nothing was flashed: the hold gesture on real glass (resistive CYD and capacitive Guition),
whether 1.5 s feels right, and whether a hold that becomes a swipe cancels cleanly.
- The write-back to a running ESP Screens (`esphome.screen_setting` event), which the firmware already
used for the Home Assistant number entities and which this page reuses unchanged.
- `Restart` from the page, and `esphome.<screen>_open_settings` from Home Assistant.