A code check found that a setting changed on a screen could jump back. ESP Screen Manager kept its own copy and sent it with every full update: after a restart of the app or Home Assistant, every hour and after every save. A change it missed while it or Home Assistant restarted came back to the old value, a reported setting was lost when save() refused a layout with a removed entity, and every step of a held - or + on the screen resent the whole screen. Firmware 0.2.49 offers every setting as an entity over the existing preference records (no migration): Night mode, Night starts/ends, 24-hour clock, Back to page 1 and after how long, Back to page 1 on standby, Swipe between pages and, on the Guition, Rotation, next to the four numbers and Auto standby. The settings page and every entity write through settings_screen::set(), which clamps like the page and stores, applies and reports only a real change. screen_message answers a caller that asks with its status and layout revision (api.respond). The settings tile opens without Home Assistant. The app reads and changes the settings of such a screen through its entities and leaves them out of the layout message. Older firmware keeps them with the layout; a setting it reports is stored without the tile check and not sent back. Pings to screens that can answer learn at once whether a tile is missing and resend after 30 s instead of 120 s. Layout sensors come back after a Home Assistant restart, and weather tiles only ask for the forecasts the entity supports. The editor's settings form is now three cards with the rows of the screen's own page; a change applies at once, and an offline screen shows its values as unknown. Checked with 245 Python and 14 C++ tests, generate_packages --check, ESP32 builds of both profiles, host-build API tests on both boards, and a live run against Home Assistant 2026.9.1 with a temporary host device. Firmware 0.2.49. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.1 KiB
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 |
| 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 |
| 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).
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, 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 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
< 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. 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::Settingsincomponents/smart_display/screen_settings.his the frozen block of eleven values that ESP Screens sends older firmware assettings. Its format is version 1 and changing it needs a preference migration and firmware-version gating in the add-on, because older firmware refuses asettingsobject that does not have exactly its own keys. Don't.- Anything new is a plain value in
settings_screen.hnext toswipe_pages,rotationandauto_home, with a preference record of its own: load it inruntime_tiles::load_settings()and write it inpersist_settings().
// 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:
else if (key == "beep") reported = beep = flag(value);
Then one line in the table of the group it belongs to:
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 both board profiles, with the same name as the row:
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. Then run python3 tools/generate_packages.py.
4. The add-on
screen_manager/app/core.py: one line inSETTING_RULES('beep': (False, None, None)) for validation, one inSETTING_ENTITIES('beep': ('switch', 'Beep on touch'), the entity name exactly as in the profiles), and the key inSETTINGS_BESIDE_BLOCKso it never enters the frozen block.screen_manager/app/server.py:settings_viewalready 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'), likeauto_homebelow firmware 0.2.44: such firmware cannot have it.screen_manager/app/static/app.js: one row inSETTING_GROUPS, with the label the screen uses.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 checksset(): add the key to its table of clamps.tests/test_screen_owned_settings.pychecks the entities in both profiles,SETTING_ENTITIES, the editor rows (labels, steps and the duration ladder againstsettings_screen.h) and the add-on's calls;tests/test_settings_view.pyandtests/test_layout.pycover the rest of the add-on side.- Render it before believing it:
.esphome/readme-render/host_build.py <board> --compilebuilds the real firmware for the Mac andrender_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.