6fb60b34d2
README_EXTENDED rendered everything from "Direct control" to "Override YAML" as raw text on GitHub since 0.2.74: the full-page pictures ended without a blank line. It also gains the media card, Page buttons, the ··· menu's Identify/Copy/Export/Import, Try it, and where SCREEN_FIRMWARE_VERSION lives now. EASY_SETUP's settings table drops Show clock and gets Back to page 1; SETTINGS.md points at web/src/store.ts and packages/core.yaml; GUITION.md had the GT911 mirrored and cache settings that left the profile on 2026-09-12; TROUBLESHOOTING still named TILE_COUNT and files deleted in 0.2.50; CALIBRATING put Calibrate touch in ESP Screens instead of Home Assistant. RELEASING gets the compatibility notes of 0.2.73 to 0.2.85 and the GitHub release step. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
171 lines
9.8 KiB
Markdown
171 lines
9.8 KiB
Markdown
# Settings: on the screen, in Home Assistant and in ESP Screens
|
|
|
|
Every screen setting can be changed in three places: the **settings page on the screen itself**
|
|
(firmware 0.2.44+), its **entity in Home Assistant** (firmware 0.2.49+), and the **Screen settings**
|
|
panel of the add-on editor. This page says who owns a value, what the page is made of, and exactly what
|
|
adding one more setting takes.
|
|
|
|
## Who owns a setting
|
|
|
|
**Firmware 0.2.49 and newer: the screen.** It keeps every value in its own preferences and offers each one
|
|
as an entity in the *config* category of its ESPHome device. The settings page, an automation and ESP Screens
|
|
all change a value through `settings_screen::set()`, so they never disagree, and nothing has to catch up
|
|
after ESP Screens or Home Assistant was away. ESP Screens reads the values from Home Assistant's states,
|
|
changes them with the entity's own action, and leaves them out of the layout message.
|
|
|
|
| Row on the screen | Entity | Key in ESP Screens |
|
|
|---|---|---|
|
|
| Brightness | `number.<screen>_normal_brightness` | `brightness` |
|
|
| Dark mode (0.2.54+) | `switch.<screen>_dark_mode` | `dark_mode` |
|
|
| Auto standby | `switch.<screen>_auto_standby` | `standby_enabled` |
|
|
| Standby after | `number.<screen>_standby_after` | `standby_seconds` |
|
|
| Standby brightness | `number.<screen>_standby_brightness` | `standby_brightness` |
|
|
| Night mode | `switch.<screen>_night_mode` | `night_enabled` |
|
|
| Starts, Ends | `time.<screen>_night_starts`, `time.<screen>_night_ends` | `night_start`, `night_end` |
|
|
| Night brightness | `number.<screen>_night_brightness` | `night_brightness` |
|
|
| Clock | `switch.<screen>_24_hour_clock` | `clock_24h` |
|
|
| Back to page 1, After | `switch.<screen>_back_to_page_1`, `number.<screen>_back_to_page_1_after` | `auto_home`, `auto_home_seconds` |
|
|
| Also on standby | `switch.<screen>_back_to_page_1_on_standby` | `home_on_standby` |
|
|
| Swipe between pages | `switch.<screen>_swipe_between_pages` | `swipe_pages` |
|
|
| Page buttons (0.2.69+) | `switch.<screen>_page_buttons` | `page_buttons` |
|
|
| Rotation (Guition) | `select.<screen>_rotation` | `rotation` |
|
|
|
|
The first four entities and Auto standby existed before 0.2.49; ESP Screens recognizes a screen that owns
|
|
its settings by one of the others (`OWNED_SETTINGS_MARKERS` in `core.py`). Dark mode came with firmware 0.2.54:
|
|
a screen without its switch shows no Dark mode row in ESP Screens, and firmware that gets its settings with the
|
|
layout never gets it at all. What Dark mode changes on the glass is in [docs/THEME.md](THEME.md).
|
|
|
|
**Older firmware: ESP Screens.** The values travel in the layout message: `settings`, the frozen block of
|
|
eleven keys, with `swipe_pages`, `auto_home`, `auto_home_seconds` and `rotation` as keys of their own. A
|
|
change on the screen comes back as an `esphome.screen_setting` event, which ESP Screens stores with the
|
|
layout without sending it back.
|
|
|
|
## 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, which opens the page with or without Home Assistant, 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, Dark mode, 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, Page buttons, Rotation (boards that turn) |
|
|
| This screen | Screen, Address, Firmware, Home Assistant, Restart |
|
|
|
|
Every change is stored on the screen, applied at once and published on its entity, so Home Assistant and
|
|
ESP Screens show it within a second. The editor's **Screen settings** panel has the first three groups as
|
|
cards with the same rows: a switch for a toggle, `-` and `+` that repeat while held, chips for the clock
|
|
and the rotation. A change there applies at once, without Save. An offline screen shows its values as
|
|
unknown and takes no changes until it is back.
|
|
|
|
## 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 pager as the tile pages: a chevron in each
|
|
half of the bar and a dot per page between them (firmware 0.2.69+). A 320x240 board shows five rows, a
|
|
480x480 board six; with the pager four and five.
|
|
- **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. Every firmware that has it also owns its
|
|
settings, so a new setting never travels in the layout message.
|
|
|
|
### 1. Where the value lives
|
|
|
|
- `screen_settings::Settings` in `components/smart_display/screen_settings.h` is the **frozen** block of
|
|
eleven values that ESP Screens sends older firmware 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 a preference record of its own: load it in `runtime_tiles::load_settings()` and write it in
|
|
`persist_settings()`.
|
|
|
|
```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;
|
|
```
|
|
|
|
### 2. The one write path, and the row
|
|
|
|
`settings_screen::set(key, value)` is where every writer ends up. Give the key a branch that clamps it the
|
|
way the page steps it, and add the value to the before/after comparison at the bottom, so a value the screen
|
|
already has is not stored, applied or reported again:
|
|
|
|
```cpp
|
|
else if (key == "beep") reported = beep = flag(value);
|
|
```
|
|
|
|
Then one line in the table of the group it belongs to:
|
|
|
|
```cpp
|
|
toggle("Beep on touch", [] { return beep; }, [](int32_t value) { set("beep", value); }),
|
|
```
|
|
|
|
`set()` calls `changed(key, value)`, which stores, 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 entity
|
|
|
|
In `packages/core.yaml`, which every board builds from, with the same name as the row:
|
|
|
|
```yaml
|
|
switch:
|
|
- platform: template
|
|
id: setting_beep
|
|
name: "Beep on touch"
|
|
entity_category: config
|
|
restore_mode: DISABLED
|
|
lambda: 'return settings_screen::beep != 0;'
|
|
turn_on_action:
|
|
- lambda: 'settings_screen::set("beep", 1);'
|
|
turn_off_action:
|
|
- lambda: 'settings_screen::set("beep", 0);'
|
|
```
|
|
|
|
The screen's preferences hold the value, so the entity reads it in a lambda instead of keeping a copy
|
|
(`restore_value` cannot be combined with a lambda). A switch publishes its own changes. A number, time or
|
|
select takes `update_interval: never` and one line in `apply_screen_settings`, which publishes it when it
|
|
differs; that script runs after every change and on every Home Assistant connection (the time sync), so the
|
|
entity has a value before Home Assistant reads the states. A setting only some boards have goes into their board
|
|
files instead, like the Rotation select of the Guition (docs/PROFILES.md).
|
|
|
|
### 4. The add-on
|
|
|
|
- `screen_manager/app/core.py`: one line in `SETTING_RULES` (`'beep': (False, None, None)`) for validation,
|
|
one in `SETTING_ENTITIES` (`'beep': ('switch', 'Beep on touch')`, the entity name exactly as in
|
|
`packages/core.yaml`), and the key in `SETTINGS_BESIDE_BLOCK` so it never enters the frozen block.
|
|
- `screen_manager/app/server.py`: `settings_view` already leaves a key out for a screen whose device has no
|
|
entity for it. Leave it out for a screen that does not own its settings too (`owner` `'layout'`), as
|
|
`dark_mode` and `page_buttons` are: such firmware cannot have it.
|
|
- `web/src/store.ts`: one row in `SETTING_GROUPS`, with the label the screen uses; then build the editor
|
|
(`cd web && npm test && npm run build`, AGENTS.md).
|
|
- `screen_manager/app/claude_skill.py`: a row in the table of screen entities.
|
|
|
|
### 5. Tests and proof
|
|
|
|
- `tests/test_settings_screen.cpp` (`clang++ -std=c++17 -Wall -Wextra -Werror -I.`) walks every page and
|
|
every row, and checks `set()`: add the key to its table of clamps.
|
|
- `tests/test_screen_owned_settings.py` checks the entities in both profiles, `SETTING_ENTITIES`, the editor
|
|
rows (labels, steps and the duration ladder against `settings_screen.h`) and the add-on's calls;
|
|
`tests/test_settings_view.py` and `tests/test_layout.py` cover the rest of 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. The host build takes its time
|
|
from the Mac instead of Home Assistant, so without a connection-time sync its numbers, times and select
|
|
get their first value at the next whole minute.
|