Let Wake only light the screen, and open ESP Screens without a chosen screen (0.2.65)

Wake from Home Assistant counted as a tap: it reset last_touch_ms, the one
clock that standby and Back to page 1 both read. An automation pressing Wake
on every motion therefore held an open card or page 2 up for as long as
someone moved in the room. Back to page 1 now has its own clock, last_use_ms,
written only by the boot, the touchscreen triggers, open_settings from Home
Assistant and the UI self test. Wake, an alert ending and Auto standby on
still restart the standby time, and Wake still brings back the brightness and
the second hand.

The rule moved from the interval into the script back_to_page_1_when_due.
Wake runs it right after waking a dimmed screen, so a page whose time ran out
during standby lights up on page 1 instead of jumping there a second later.

ESP Screens no longer opens the first screen by itself. Until one is picked
the page says "Choose a screen", and with no screens it still offers the
install; both cards start hidden so neither flashes while the page loads.

Checked with 48 host API checks per board, both ESP32 builds, 314 Python and
16 C++ tests, and the editor on the demo home. Firmware 0.2.56.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-17 12:41:19 +02:00
parent e68162bd86
commit dfd1a26d69
17 changed files with 400 additions and 110 deletions
+5 -2
View File
@@ -229,8 +229,11 @@ written until you press the button.
Every screen has two buttons in Home Assistant (firmware 0.2.45+). An automation presses them
with the `button.press` action:
- **`button.<screen>_wake`** does what a tap does: a screen in standby lights up, and the standby
time starts counting again. On a screen that is already on, only the count starts again.
- **`button.<screen>_wake`** lights the screen up: a screen in standby goes to its normal brightness
(the second hand of an analog clock runs again), and the standby time starts counting again. On a
screen that is already on, only the count starts again. Wake is not a touch: **Back to page 1** keeps
counting from the last touch, so pressing Wake on every motion doesn't keep an open card or a later
page up, and a screen whose time ran out during standby lights up on page 1 (firmware 0.2.56+).
- **`button.<screen>_sleep`** puts the screen in standby right away, the same as when the standby
time runs out, and also works with **Auto standby** off. The screen stays in standby until someone
taps it, **Wake** is pressed or an alert comes in; switching Auto standby off doesn't end it. An alert
+23
View File
@@ -176,6 +176,29 @@ 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.65 / firmware 0.2.56
Both board profiles and the editor page; the app raises `FIRMWARE_VERSION`. Storage, tile protocol, preferences, keys
and entities are unchanged.
- Two clocks. `last_touch_ms` stays the standby time: a touch, Wake, `wake_display`, an alert ending
(`alert_dismiss`) and Auto standby on restart it, exactly as before. The new non-restored `last_use_ms` is Back to
page 1: the boot, the three touchscreen triggers (`on_touch`, `on_update`, `on_release`), `open_settings` from Home
Assistant and the UI self test write it, nothing else. Before, Wake reset the one clock both rules read, so an
automation pressing Wake on every motion held a card or page 2 up indefinitely.
- The Back to page 1 check moved from the one-second interval into the script `back_to_page_1_when_due` (same
condition, now on `last_use_ms`, still paused while `display_dimmed`). The interval runs it, and so does Wake
right after `wake_display` on a dimmed screen: a page whose time ran out during standby goes home in the same pass,
before LVGL draws the old page at full brightness. `ui_state` logs `use_idle_ms` beside `idle_ms`.
- `wake_display` is unchanged and still restarts only the standby clock: a tap on the dim overlay is a touch through
the touchscreen triggers anyway, and an alert blocks the rule while it is up.
- Older firmware keeps counting Wake, an alert ending and Auto standby on as a touch for Back to page 1. The Claude
skill says that from 0.2.56 Wake is no touch; an installed skill shows as outdated until it is installed again.
- `static/app.js` no longer selects `inventory.screens[0]` in `refresh()` or `applyLive()`; the only way into a screen
is its button. `renderScreens()` shows `#choose` ("Choose a screen") while nothing is selected and screens exist,
`#empty` while there are none; both start `hidden` in `index.html`, so neither flashes before the first inventory.
A screen picked before the full inventory arrives still gets the 0.2.58 catalogue redraw.
### Compatibility 0.2.64 / firmware 0.2.55
Both board profiles, the Claude skill and the editor's alert tips; the app raises `FIRMWARE_VERSION`. Storage, tile
+83
View File
@@ -0,0 +1,83 @@
# Test results app 0.2.65 / firmware 0.2.56 (2026-09-17)
Two requests from Max. The Wake button in Home Assistant should only bring back the light (and the second hand of an
analog clock), not postpone Back to page 1. And ESP Screens should open without choosing a screen by itself. See
CHANGELOG 0.2.65 and the compatibility note in docs/RELEASING.md. Built on 0.2.64 (`e68162b`).
## Cause
- Wake (0.2.53) was designed as "a tap": on a dimmed screen it ran `wake_display`, on a screen that was on it set
`last_touch_ms`. That one clock also drove the Back to page 1 check in the one-second interval, so every Wake started
the two minutes again. An automation pressing Wake on every motion kept an open card or page 2 up as long as someone
moved in the room. The same held for an alert ending and for Auto standby switched on.
- `refresh()` and `applyLive()` in `static/app.js` selected `inventory.screens[0]` whenever nothing was selected, since
the first version of the editor.
## Automated
- Python (`.venv-portal`): 314 tests OK (312 on 0.2.64). `tests/test_wake_sleep.py`: `test_wake_is_a_tap` became
`test_wake_is_light_not_a_tap` (Wake runs `wake_display` and then `back_to_page_1_when_due`, and neither touches
`last_use_ms`), and the new `test_back_to_page_1_counts_from_the_last_touch` checks in both profiles and both packages
that the rule reads `last_use_ms` only, runs from the interval and Wake only, and that exactly the boot, the three
touchscreen triggers, `open_settings` and the UI self test write that clock, while `alert_dismiss` and Auto standby
write only `last_touch_ms`. `tests/test_editor_startup.py`: no `select()` besides the screen button, the
"Choose a screen" card and the install card start hidden and follow the selection, and the 0.2.58 catalogue redraw
stays.
- C++ (`clang++ -std=c++17 -Wall -Wextra -Werror -I.`): 16/16 PASS.
- `generate_packages.py --check` green.
## Firmware on the Mac (host builds, driven over the ESPHome API)
The 0.2.53 Wake/Sleep harness on host builds of both packages (own device names and API ports), with a probe that
also logs `tile_page`, the age of `last_use_ms`, `runtime_tiles::awake()` (what the second hand follows) and the Back
to page 1 setting. **Guition 48/48, CYD 48/48.** The 35 earlier checks (standby timer, Sleep with Auto standby off
across a real minute tick, alert, calibration) all still pass. The 13 new ones:
| Check | Result on both boards |
|---|---|
| Back to page 1 on at its default | on, 120 s |
| Wake on page 2, touch 100 s ago | standby time restarts (`idle_ms` < 3 s), page-1 clock keeps its 100 s |
| Wake again with the page-1 clock at 125 s | page 1 within the next interval pass, screen stays on |
| A card open with the clock at 125 s, Wake pressed three times | the card closes anyway |
| Sleep on page 2 with "Also on standby" off | dimmed, page 2 kept, `awake()` false |
| Page-1 clock at 300 s while in standby | still page 2: the rule waits for the light |
| Wake after that | in the same pass: bright, page 1, `awake()` true (the probe right after it logs `idle_ms=0`) |
| Sleep and Wake with 30 s on the clock | page 2 stays |
| `open_settings` from Home Assistant with the clock at 300 s | the page stays open, clock restarted |
| `open_settings` -1 | closes, page 1 |
| An alert on page 2 with the clock at 300 s | page 2 kept while the alert is up |
| The alert dismissed | page 1, standby time restarted |
A real touch cannot be injected into the host's SDL touchscreen; the profile test covers the three touchscreen
triggers that write the page-1 clock.
## Builds (ESPHome 2026.6.2)
Check profiles with the generated packages and local components, as for 0.2.64. Both compile without errors.
| Profile | RAM | Flash | vs 0.2.64 |
|---|---|---|---|
| CYD | 23.7% (77,572 B) | 77.5% (1,421,263 B) | +72 B RAM, +272 B flash |
| Guition | 24.7% (80,932 B) | 18.8% (1,524,939 B) | +80 B RAM, +292 B flash |
(The 0.2.64 numbers come from that release's check builds, whose device names were five characters shorter.)
## ESP Screens
The real add-on from this tree on the demo home (two screens, fake Home Assistant), in the browser pane:
- Opening the page: nothing selected, the right side says "Choose a screen", the install card and the editor hidden.
The Refresh button and a light inventory select nothing.
- Clicking a screen opens the editor with its top bar, tiles and the entity list (34 results), and marks it in the
list. With an unsaved name, clicking the other screen asks "You have unsaved changes. Open a different screen
anyway?"; No keeps the screen, Yes opens the other one. Live updates afterwards keep that choice.
- After a reload nothing is selected again. A live update with no screens shows "Start with a screen" with its button;
with the screens back, "Choose a screen" returns. Settings and back to My screens keep the empty choice.
- At 375 px the screens sit in their row above a short "Choose a screen" card (no tall empty card, no icon, no
horizontal scroll); tapping a screen opens the editor.
- No console errors.
## Not tested
- Physical screens: not flashed in this round. Wake on Studio 1 from Home Assistant after the update is Max's check.
- The page in Home Assistant's Ingress frame.
+44 -21
View File
@@ -2,7 +2,7 @@
# Source profile for packages/guition.yaml (tools/generate_packages.py); see docs/GUITION.md.
# A screen gets its tiles from the ESP Screen Manager add-on while it runs. The CYD is its own profile.
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.55"
SCREEN_FIRMWARE_VERSION: "0.2.56"
# 0: no drift limit; LVGL decides, releasing inside the tile is a tap, leaving it is not
# (like iOS). The CYD keeps a limit because its resistive panel jumps.
TOUCH_MOVE_LIMIT_PX: "0"
@@ -142,6 +142,7 @@ esphome:
}
}
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
id(display_dimmed) = false;
- lambda: |-
auto &settings = screen_settings::current;
@@ -372,11 +373,11 @@ api:
then:
- lambda: |-
const auto &cfg = screen_settings::current;
ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u dimmed=%d",
ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u use_idle_ms=%u dimmed=%d",
cfg.standby_enabled, cfg.standby_seconds, cfg.brightness, cfg.standby_brightness,
cfg.night_enabled, cfg.night_start, cfg.night_end, cfg.night_brightness,
cfg.show_clock, cfg.clock_24h, cfg.home_on_standby,
(unsigned)(millis() - id(last_touch_ms)), id(display_dimmed));
(unsigned)(millis() - id(last_touch_ms)), (unsigned)(millis() - id(last_use_ms)), id(display_dimmed));
ESP_LOGI("ui_diag", "navigation page=%d runtime_detail=%d", id(tile_page),
runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
ESP_LOGI("ui_diag", "rotation=%u actual=%u", (unsigned)runtime_tiles::rotation, (unsigned)id(screen_lvgl).get_rotation());
@@ -415,6 +416,8 @@ api:
then:
- lambda: |-
if (page < 0) { id(go_home).execute(); return; }
// Opened for someone to look at: Back to page 1 counts from now, as after a touch.
id(last_use_ms) = millis();
id(wake_display).execute();
settings_screen::open((uint8_t) page);
ESP_LOGI("settings", "page %d open from Home Assistant", page);
@@ -529,19 +532,7 @@ interval:
return (millis() - id(last_touch_ms)) > (screen_settings::current.standby_seconds * 1000UL);
then:
- script.execute: dim_display
# Back to page 1 by itself (0.2.44+). A card, a settings page or a second page left open keeps
# showing what someone looked at minutes ago; after this long the screen is a screen again.
# Independent of standby, which may be off or much later.
- if:
condition:
lambda: |-
if (!runtime_tiles::auto_home || id(touch_down) || id(calibration_active) || id(alert_active)) return false;
if (id(display_dimmed)) return false;
bool away = settings_screen::visible() || !id(active_entity).empty() || id(tile_page) != 0 ||
(runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
return away && (millis() - id(last_touch_ms)) > (uint32_t) runtime_tiles::auto_home_seconds * 1000UL;
then:
- script.execute: go_home
- script.execute: back_to_page_1_when_due
time:
- platform: homeassistant
@@ -606,11 +597,18 @@ globals:
type: std::string
restore_value: no
# Two clocks. last_touch_ms is the standby time: a touch, Wake, an alert ending and Auto standby on restart it.
# last_use_ms is Back to page 1 (firmware 0.2.56+): only a touch, or Home Assistant opening the settings page.
- id: last_touch_ms
type: uint32_t
restore_value: no
initial_value: '0'
- id: last_use_ms
type: uint32_t
restore_value: no
initial_value: '0'
# Debounce for do_tile_action: protects against repeated events from
# multiple separate press/release cycles for one physical touch (noise in
# the resistive layer), causing the same HA action to fire multiple times
@@ -1283,6 +1281,7 @@ touchscreen:
on_touch:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
id(touch_down) = true;
cyd::touch_guard.begin(millis(), touch.x, touch.y, touch.id);
cyd::edge_swipe.begin(touch.x, touch.y, static_cast<int>(id(screen_lvgl).get_rotation()));
@@ -1290,6 +1289,7 @@ touchscreen:
on_update:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
// Only the contact that started the touch counts; the guard ignores other ids.
for (const auto &point : touches) {
// Trace every sample of an edge touch: the log then shows how often the panel delivers.
@@ -1317,6 +1317,7 @@ touchscreen:
on_release:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
id(touch_down) = false;
if (cyd::edge_swipe.armed() && cyd::edge_swipe.inward() > 0)
ESP_LOGI("touch", "edge swipe not fired: %d px inward, %d px vertical", cyd::edge_swipe.inward(), cyd::edge_swipe.sideways());
@@ -3513,6 +3514,7 @@ script:
then:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
static unsigned test_page=0;
id(tile_page) = runtime_tiles::enabled ? test_page++ % runtime_tiles::page_count() : 1;
- script.execute: show_tile_page
@@ -3655,6 +3657,24 @@ script:
- lambda: 'id(tile_page) = 0;'
- script.execute: show_tile_page
# Back to page 1 by itself (0.2.44+). A card, a settings page or a second page left open keeps
# showing what someone looked at minutes ago; after this long the screen is a screen again.
# Independent of standby, which may be off or much later; paused while the screen is in standby.
# The time runs from the last touch (last_use_ms, firmware 0.2.56+): Wake, an alert or Auto standby
# light the screen and restart the standby time, but nobody used a page. Checked every second and by Wake.
- id: back_to_page_1_when_due
then:
- if:
condition:
lambda: |-
if (!runtime_tiles::auto_home || id(touch_down) || id(calibration_active) || id(alert_active)) return false;
if (id(display_dimmed)) return false;
bool away = settings_screen::visible() || !id(active_entity).empty() || id(tile_page) != 0 ||
(runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
return away && (millis() - id(last_use_ms)) > (uint32_t) runtime_tiles::auto_home_seconds * 1000UL;
then:
- script.execute: go_home
# ---------- ALERT (HA action show_alert) ----------
# The screen wakes, keeps its normal brightness and shows the card until OK or the
# timeout (0: until OK); the standby interval waits on alert_active. A new alert
@@ -4526,11 +4546,12 @@ text:
internal: ${DASHBOARD_INBOX_INTERNAL}
button:
# Wake and Sleep for Home Assistant automations (0.2.45+). Wake is a tap: it lights a dimmed
# screen and counts the standby time from now. Sleep is the standby time running out, right away
# and also with Auto standby off; it holds until a tap, Wake or an alert, and closes an alert that
# is up. Neither saves anything, so an automation may press them on every motion. No
# entity_category: they are controls, not settings.
# Wake and Sleep for Home Assistant automations (0.2.45+). Wake is light, not a tap (0.2.56+): it
# lights a dimmed screen and counts the standby time from now, while Back to page 1 keeps counting
# from the last touch, so Wake on every motion no longer holds a card or page 2 up. Sleep is the
# standby time running out, right away and also with Auto standby off; it holds until a tap, Wake
# or an alert, and closes an alert that is up. Neither saves anything, so an automation may press
# them on every motion. No entity_category: they are controls, not settings.
- platform: template
id: wake_button
name: "Wake"
@@ -4542,6 +4563,8 @@ button:
lambda: 'return id(display_dimmed);'
then:
- script.execute: wake_display
# A page whose time ran out during standby: light up on page 1, not on the old page first.
- script.execute: back_to_page_1_when_due
else:
- lambda: 'id(last_touch_ms) = millis();'
- platform: template
+44 -21
View File
@@ -17,7 +17,7 @@
############################################################
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.55"
SCREEN_FIRMWARE_VERSION: "0.2.56"
# A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe.
TOUCH_MOVE_LIMIT_PX: "56"
# Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not.
@@ -248,6 +248,7 @@ esphome:
id(ts_touch).set_raw_correction(${TOUCH_AFFINE_XX}f, ${TOUCH_AFFINE_XY}f, ${TOUCH_AFFINE_XC}f, ${TOUCH_AFFINE_YX}f, ${TOUCH_AFFINE_YY}f, ${TOUCH_AFFINE_YC}f);
cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS});
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
id(display_dimmed) = false;
- lambda: |-
auto &settings = screen_settings::current;
@@ -485,11 +486,11 @@ api:
then:
- lambda: |-
const auto &cfg = screen_settings::current;
ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u dimmed=%d",
ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u use_idle_ms=%u dimmed=%d",
cfg.standby_enabled, cfg.standby_seconds, cfg.brightness, cfg.standby_brightness,
cfg.night_enabled, cfg.night_start, cfg.night_end, cfg.night_brightness,
cfg.show_clock, cfg.clock_24h, cfg.home_on_standby,
(unsigned)(millis() - id(last_touch_ms)), id(display_dimmed));
(unsigned)(millis() - id(last_touch_ms)), (unsigned)(millis() - id(last_use_ms)), id(display_dimmed));
ESP_LOGI("ui_diag", "navigation page=%d runtime_detail=%d", id(tile_page),
runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
lv_area_t nav, grid;
@@ -527,6 +528,8 @@ api:
then:
- lambda: |-
if (page < 0) { id(go_home).execute(); return; }
// Opened for someone to look at: Back to page 1 counts from now, as after a touch.
id(last_use_ms) = millis();
id(wake_display).execute();
settings_screen::open((uint8_t) page);
ESP_LOGI("settings", "page %d open from Home Assistant", page);
@@ -641,19 +644,7 @@ interval:
return (millis() - id(last_touch_ms)) > (screen_settings::current.standby_seconds * 1000UL);
then:
- script.execute: dim_display
# Back to page 1 by itself (0.2.44+). A card, a settings page or a second page left open keeps
# showing what someone looked at minutes ago; after this long the screen is a screen again.
# Independent of standby, which may be off or much later.
- if:
condition:
lambda: |-
if (!runtime_tiles::auto_home || id(touch_down) || id(calibration_active) || id(alert_active)) return false;
if (id(display_dimmed)) return false;
bool away = settings_screen::visible() || !id(active_entity).empty() || id(tile_page) != 0 ||
(runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
return away && (millis() - id(last_touch_ms)) > (uint32_t) runtime_tiles::auto_home_seconds * 1000UL;
then:
- script.execute: go_home
- script.execute: back_to_page_1_when_due
time:
- platform: homeassistant
@@ -718,11 +709,18 @@ globals:
type: std::string
restore_value: no
# Two clocks. last_touch_ms is the standby time: a touch, Wake, an alert ending and Auto standby on restart it.
# last_use_ms is Back to page 1 (firmware 0.2.56+): only a touch, or Home Assistant opening the settings page.
- id: last_touch_ms
type: uint32_t
restore_value: no
initial_value: '0'
- id: last_use_ms
type: uint32_t
restore_value: no
initial_value: '0'
# Debounce for do_tile_action: this resistive XPT2046 panel sometimes gives
# multiple separate press/release cycles for one physical touch (noise in
# the resistive layer), causing the same HA action to fire multiple times
@@ -1414,6 +1412,7 @@ touchscreen:
on_touch:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
id(touch_down) = true;
cyd::touch_guard.begin(millis(), touch.x, touch.y, touch.id);
screen_calibration::press();
@@ -1421,6 +1420,7 @@ touchscreen:
on_update:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
// Only the contact that started the touch counts; the guard ignores other ids.
for (const auto &point : touches) {
cyd::touch_guard.update(point.x, point.y, point.id);
@@ -1428,6 +1428,7 @@ touchscreen:
on_release:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
id(touch_down) = false;
screen_calibration::release(id(ts_touch).filtered_raw_x(), id(ts_touch).filtered_raw_y());
@@ -3526,6 +3527,7 @@ script:
then:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
static unsigned test_page=0;
id(tile_page) = runtime_tiles::enabled ? test_page++ % runtime_tiles::page_count() : 1;
- script.execute: show_tile_page
@@ -3656,6 +3658,24 @@ script:
- lambda: 'id(tile_page) = 0;'
- script.execute: show_tile_page
# Back to page 1 by itself (0.2.44+). A card, a settings page or a second page left open keeps
# showing what someone looked at minutes ago; after this long the screen is a screen again.
# Independent of standby, which may be off or much later; paused while the screen is in standby.
# The time runs from the last touch (last_use_ms, firmware 0.2.56+): Wake, an alert or Auto standby
# light the screen and restart the standby time, but nobody used a page. Checked every second and by Wake.
- id: back_to_page_1_when_due
then:
- if:
condition:
lambda: |-
if (!runtime_tiles::auto_home || id(touch_down) || id(calibration_active) || id(alert_active)) return false;
if (id(display_dimmed)) return false;
bool away = settings_screen::visible() || !id(active_entity).empty() || id(tile_page) != 0 ||
(runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
return away && (millis() - id(last_use_ms)) > (uint32_t) runtime_tiles::auto_home_seconds * 1000UL;
then:
- script.execute: go_home
# ---------- ALERT (HA action show_alert) ----------
# The screen wakes, keeps its normal brightness and shows the card until OK or the
# timeout (0: until OK); the standby interval waits on alert_active. A new alert
@@ -4494,11 +4514,12 @@ button:
entity_category: config
on_press:
- lambda: 'if (runtime_tiles::enabled) screen_calibration::begin();'
# Wake and Sleep for Home Assistant automations (0.2.45+). Wake is a tap: it lights a dimmed
# screen and counts the standby time from now. Sleep is the standby time running out, right away
# and also with Auto standby off; it holds until a tap, Wake or an alert, and closes an alert that
# is up. Neither saves anything, so an automation may press them on every motion. No
# entity_category: they are controls, not settings.
# Wake and Sleep for Home Assistant automations (0.2.45+). Wake is light, not a tap (0.2.56+): it
# lights a dimmed screen and counts the standby time from now, while Back to page 1 keeps counting
# from the last touch, so Wake on every motion no longer holds a card or page 2 up. Sleep is the
# standby time running out, right away and also with Auto standby off; it holds until a tap, Wake
# or an alert, and closes an alert that is up. Neither saves anything, so an automation may press
# them on every motion. No entity_category: they are controls, not settings.
- platform: template
id: wake_button
name: "Wake"
@@ -4510,6 +4531,8 @@ button:
lambda: 'return id(display_dimmed);'
then:
- script.execute: wake_display
# A page whose time ran out during standby: light up on page 1, not on the old page first.
- script.execute: back_to_page_1_when_due
else:
- lambda: 'id(last_touch_ms) = millis();'
- platform: template
+44 -21
View File
@@ -18,7 +18,7 @@
############################################################
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.55"
SCREEN_FIRMWARE_VERSION: "0.2.56"
# A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe.
TOUCH_MOVE_LIMIT_PX: "56"
# Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not.
@@ -252,6 +252,7 @@ esphome:
id(ts_touch).set_raw_correction(${TOUCH_AFFINE_XX}f, ${TOUCH_AFFINE_XY}f, ${TOUCH_AFFINE_XC}f, ${TOUCH_AFFINE_YX}f, ${TOUCH_AFFINE_YY}f, ${TOUCH_AFFINE_YC}f);
cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS});
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
id(display_dimmed) = false;
- lambda: |-
auto &settings = screen_settings::current;
@@ -489,11 +490,11 @@ api:
then:
- lambda: |-
const auto &cfg = screen_settings::current;
ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u dimmed=%d",
ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u use_idle_ms=%u dimmed=%d",
cfg.standby_enabled, cfg.standby_seconds, cfg.brightness, cfg.standby_brightness,
cfg.night_enabled, cfg.night_start, cfg.night_end, cfg.night_brightness,
cfg.show_clock, cfg.clock_24h, cfg.home_on_standby,
(unsigned)(millis() - id(last_touch_ms)), id(display_dimmed));
(unsigned)(millis() - id(last_touch_ms)), (unsigned)(millis() - id(last_use_ms)), id(display_dimmed));
ESP_LOGI("ui_diag", "navigation page=%d runtime_detail=%d", id(tile_page),
runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
lv_area_t nav, grid;
@@ -531,6 +532,8 @@ api:
then:
- lambda: |-
if (page < 0) { id(go_home).execute(); return; }
// Opened for someone to look at: Back to page 1 counts from now, as after a touch.
id(last_use_ms) = millis();
id(wake_display).execute();
settings_screen::open((uint8_t) page);
ESP_LOGI("settings", "page %d open from Home Assistant", page);
@@ -630,19 +633,7 @@ interval:
return (millis() - id(last_touch_ms)) > (screen_settings::current.standby_seconds * 1000UL);
then:
- script.execute: dim_display
# Back to page 1 by itself (0.2.44+). A card, a settings page or a second page left open keeps
# showing what someone looked at minutes ago; after this long the screen is a screen again.
# Independent of standby, which may be off or much later.
- if:
condition:
lambda: |-
if (!runtime_tiles::auto_home || id(touch_down) || id(calibration_active) || id(alert_active)) return false;
if (id(display_dimmed)) return false;
bool away = settings_screen::visible() || !id(active_entity).empty() || id(tile_page) != 0 ||
(runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
return away && (millis() - id(last_touch_ms)) > (uint32_t) runtime_tiles::auto_home_seconds * 1000UL;
then:
- script.execute: go_home
- script.execute: back_to_page_1_when_due
time:
- platform: homeassistant
@@ -707,11 +698,18 @@ globals:
type: std::string
restore_value: no
# Two clocks. last_touch_ms is the standby time: a touch, Wake, an alert ending and Auto standby on restart it.
# last_use_ms is Back to page 1 (firmware 0.2.56+): only a touch, or Home Assistant opening the settings page.
- id: last_touch_ms
type: uint32_t
restore_value: no
initial_value: '0'
- id: last_use_ms
type: uint32_t
restore_value: no
initial_value: '0'
# Debounce for do_tile_action: this resistive XPT2046 panel sometimes gives
# multiple separate press/release cycles for one physical touch (noise in
# the resistive layer), causing the same HA action to fire multiple times
@@ -1403,6 +1401,7 @@ touchscreen:
on_touch:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
id(touch_down) = true;
cyd::touch_guard.begin(millis(), touch.x, touch.y, touch.id);
screen_calibration::press();
@@ -1410,6 +1409,7 @@ touchscreen:
on_update:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
// Only the contact that started the touch counts; the guard ignores other ids.
for (const auto &point : touches) {
cyd::touch_guard.update(point.x, point.y, point.id);
@@ -1417,6 +1417,7 @@ touchscreen:
on_release:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
id(touch_down) = false;
screen_calibration::release(id(ts_touch).filtered_raw_x(), id(ts_touch).filtered_raw_y());
@@ -3515,6 +3516,7 @@ script:
then:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
static unsigned test_page=0;
id(tile_page) = runtime_tiles::enabled ? test_page++ % runtime_tiles::page_count() : 1;
- script.execute: show_tile_page
@@ -3645,6 +3647,24 @@ script:
- lambda: 'id(tile_page) = 0;'
- script.execute: show_tile_page
# Back to page 1 by itself (0.2.44+). A card, a settings page or a second page left open keeps
# showing what someone looked at minutes ago; after this long the screen is a screen again.
# Independent of standby, which may be off or much later; paused while the screen is in standby.
# The time runs from the last touch (last_use_ms, firmware 0.2.56+): Wake, an alert or Auto standby
# light the screen and restart the standby time, but nobody used a page. Checked every second and by Wake.
- id: back_to_page_1_when_due
then:
- if:
condition:
lambda: |-
if (!runtime_tiles::auto_home || id(touch_down) || id(calibration_active) || id(alert_active)) return false;
if (id(display_dimmed)) return false;
bool away = settings_screen::visible() || !id(active_entity).empty() || id(tile_page) != 0 ||
(runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
return away && (millis() - id(last_use_ms)) > (uint32_t) runtime_tiles::auto_home_seconds * 1000UL;
then:
- script.execute: go_home
# ---------- ALERT (HA action show_alert) ----------
# The screen wakes, keeps its normal brightness and shows the card until OK or the
# timeout (0: until OK); the standby interval waits on alert_active. A new alert
@@ -4483,11 +4503,12 @@ button:
entity_category: config
on_press:
- lambda: 'if (runtime_tiles::enabled) screen_calibration::begin();'
# Wake and Sleep for Home Assistant automations (0.2.45+). Wake is a tap: it lights a dimmed
# screen and counts the standby time from now. Sleep is the standby time running out, right away
# and also with Auto standby off; it holds until a tap, Wake or an alert, and closes an alert that
# is up. Neither saves anything, so an automation may press them on every motion. No
# entity_category: they are controls, not settings.
# Wake and Sleep for Home Assistant automations (0.2.45+). Wake is light, not a tap (0.2.56+): it
# lights a dimmed screen and counts the standby time from now, while Back to page 1 keeps counting
# from the last touch, so Wake on every motion no longer holds a card or page 2 up. Sleep is the
# standby time running out, right away and also with Auto standby off; it holds until a tap, Wake
# or an alert, and closes an alert that is up. Neither saves anything, so an automation may press
# them on every motion. No entity_category: they are controls, not settings.
- platform: template
id: wake_button
name: "Wake"
@@ -4499,6 +4520,8 @@ button:
lambda: 'return id(display_dimmed);'
then:
- script.execute: wake_display
# A page whose time ran out during standby: light up on page 1, not on the old page first.
- script.execute: back_to_page_1_when_due
else:
- lambda: 'id(last_touch_ms) = millis();'
- platform: template
+44 -21
View File
@@ -3,7 +3,7 @@
# Source profile for packages/guition.yaml (tools/generate_packages.py); see docs/GUITION.md.
# A screen gets its tiles from the ESP Screen Manager add-on while it runs. The CYD is its own profile.
substitutions:
SCREEN_FIRMWARE_VERSION: "0.2.55"
SCREEN_FIRMWARE_VERSION: "0.2.56"
# 0: no drift limit; LVGL decides, releasing inside the tile is a tap, leaving it is not
# (like iOS). The CYD keeps a limit because its resistive panel jumps.
TOUCH_MOVE_LIMIT_PX: "0"
@@ -146,6 +146,7 @@ esphome:
}
}
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
id(display_dimmed) = false;
- lambda: |-
auto &settings = screen_settings::current;
@@ -376,11 +377,11 @@ api:
then:
- lambda: |-
const auto &cfg = screen_settings::current;
ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u dimmed=%d",
ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u use_idle_ms=%u dimmed=%d",
cfg.standby_enabled, cfg.standby_seconds, cfg.brightness, cfg.standby_brightness,
cfg.night_enabled, cfg.night_start, cfg.night_end, cfg.night_brightness,
cfg.show_clock, cfg.clock_24h, cfg.home_on_standby,
(unsigned)(millis() - id(last_touch_ms)), id(display_dimmed));
(unsigned)(millis() - id(last_touch_ms)), (unsigned)(millis() - id(last_use_ms)), id(display_dimmed));
ESP_LOGI("ui_diag", "navigation page=%d runtime_detail=%d", id(tile_page),
runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
ESP_LOGI("ui_diag", "rotation=%u actual=%u", (unsigned)runtime_tiles::rotation, (unsigned)id(screen_lvgl).get_rotation());
@@ -419,6 +420,8 @@ api:
then:
- lambda: |-
if (page < 0) { id(go_home).execute(); return; }
// Opened for someone to look at: Back to page 1 counts from now, as after a touch.
id(last_use_ms) = millis();
id(wake_display).execute();
settings_screen::open((uint8_t) page);
ESP_LOGI("settings", "page %d open from Home Assistant", page);
@@ -518,19 +521,7 @@ interval:
return (millis() - id(last_touch_ms)) > (screen_settings::current.standby_seconds * 1000UL);
then:
- script.execute: dim_display
# Back to page 1 by itself (0.2.44+). A card, a settings page or a second page left open keeps
# showing what someone looked at minutes ago; after this long the screen is a screen again.
# Independent of standby, which may be off or much later.
- if:
condition:
lambda: |-
if (!runtime_tiles::auto_home || id(touch_down) || id(calibration_active) || id(alert_active)) return false;
if (id(display_dimmed)) return false;
bool away = settings_screen::visible() || !id(active_entity).empty() || id(tile_page) != 0 ||
(runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
return away && (millis() - id(last_touch_ms)) > (uint32_t) runtime_tiles::auto_home_seconds * 1000UL;
then:
- script.execute: go_home
- script.execute: back_to_page_1_when_due
time:
- platform: homeassistant
@@ -595,11 +586,18 @@ globals:
type: std::string
restore_value: no
# Two clocks. last_touch_ms is the standby time: a touch, Wake, an alert ending and Auto standby on restart it.
# last_use_ms is Back to page 1 (firmware 0.2.56+): only a touch, or Home Assistant opening the settings page.
- id: last_touch_ms
type: uint32_t
restore_value: no
initial_value: '0'
- id: last_use_ms
type: uint32_t
restore_value: no
initial_value: '0'
# Debounce for do_tile_action: protects against repeated events from
# multiple separate press/release cycles for one physical touch (noise in
# the resistive layer), causing the same HA action to fire multiple times
@@ -1272,6 +1270,7 @@ touchscreen:
on_touch:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
id(touch_down) = true;
cyd::touch_guard.begin(millis(), touch.x, touch.y, touch.id);
cyd::edge_swipe.begin(touch.x, touch.y, static_cast<int>(id(screen_lvgl).get_rotation()));
@@ -1279,6 +1278,7 @@ touchscreen:
on_update:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
// Only the contact that started the touch counts; the guard ignores other ids.
for (const auto &point : touches) {
// Trace every sample of an edge touch: the log then shows how often the panel delivers.
@@ -1306,6 +1306,7 @@ touchscreen:
on_release:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
id(touch_down) = false;
if (cyd::edge_swipe.armed() && cyd::edge_swipe.inward() > 0)
ESP_LOGI("touch", "edge swipe not fired: %d px inward, %d px vertical", cyd::edge_swipe.inward(), cyd::edge_swipe.sideways());
@@ -3502,6 +3503,7 @@ script:
then:
- lambda: |-
id(last_touch_ms) = millis();
id(last_use_ms) = millis();
static unsigned test_page=0;
id(tile_page) = runtime_tiles::enabled ? test_page++ % runtime_tiles::page_count() : 1;
- script.execute: show_tile_page
@@ -3644,6 +3646,24 @@ script:
- lambda: 'id(tile_page) = 0;'
- script.execute: show_tile_page
# Back to page 1 by itself (0.2.44+). A card, a settings page or a second page left open keeps
# showing what someone looked at minutes ago; after this long the screen is a screen again.
# Independent of standby, which may be off or much later; paused while the screen is in standby.
# The time runs from the last touch (last_use_ms, firmware 0.2.56+): Wake, an alert or Auto standby
# light the screen and restart the standby time, but nobody used a page. Checked every second and by Wake.
- id: back_to_page_1_when_due
then:
- if:
condition:
lambda: |-
if (!runtime_tiles::auto_home || id(touch_down) || id(calibration_active) || id(alert_active)) return false;
if (id(display_dimmed)) return false;
bool away = settings_screen::visible() || !id(active_entity).empty() || id(tile_page) != 0 ||
(runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
return away && (millis() - id(last_use_ms)) > (uint32_t) runtime_tiles::auto_home_seconds * 1000UL;
then:
- script.execute: go_home
# ---------- ALERT (HA action show_alert) ----------
# The screen wakes, keeps its normal brightness and shows the card until OK or the
# timeout (0: until OK); the standby interval waits on alert_active. A new alert
@@ -4515,11 +4535,12 @@ text:
internal: ${DASHBOARD_INBOX_INTERNAL}
button:
# Wake and Sleep for Home Assistant automations (0.2.45+). Wake is a tap: it lights a dimmed
# screen and counts the standby time from now. Sleep is the standby time running out, right away
# and also with Auto standby off; it holds until a tap, Wake or an alert, and closes an alert that
# is up. Neither saves anything, so an automation may press them on every motion. No
# entity_category: they are controls, not settings.
# Wake and Sleep for Home Assistant automations (0.2.45+). Wake is light, not a tap (0.2.56+): it
# lights a dimmed screen and counts the standby time from now, while Back to page 1 keeps counting
# from the last touch, so Wake on every motion no longer holds a card or page 2 up. Sleep is the
# standby time running out, right away and also with Auto standby off; it holds until a tap, Wake
# or an alert, and closes an alert that is up. Neither saves anything, so an automation may press
# them on every motion. No entity_category: they are controls, not settings.
- platform: template
id: wake_button
name: "Wake"
@@ -4531,6 +4552,8 @@ button:
lambda: 'return id(display_dimmed);'
then:
- script.execute: wake_display
# A page whose time ran out during standby: light up on page 1, not on the old page first.
- script.execute: back_to_page_1_when_due
else:
- lambda: 'id(last_touch_ms) = millis();'
- platform: template
+11
View File
@@ -1,3 +1,14 @@
## 0.2.65 (firmware 0.2.56)
Wake lights the screen, and ESP Screens lets you pick the screen.
- **Wake is for the light.** The **Wake** button in Home Assistant still turns a screen in standby up to its normal brightness, brings back the second hand of an analog clock and starts the standby time again. It no longer counts as a touch for **Back to page 1**: that keeps counting from the last time someone touched the screen. An automation that presses Wake on every motion used to keep an open card or page 2 up for as long as someone moved in the room; now the screen goes back to page 1 on time.
- **Straight to page 1 after standby.** When the Back to page 1 time ran out while the screen was in standby, Wake lights it up on page 1 at once, instead of showing the old page for a moment first. With time left, the page stays.
- **Only a touch counts.** An alert that closes by itself and turning Auto standby on also start the standby time again, but not the Back to page 1 time. Opening the settings page from Home Assistant (`open_settings`) still counts, so that page stays open.
- **ESP Screens opens without a chosen screen.** It used to open the first screen in the list; now you pick one. Until then the page says **Choose a screen**, and with no screens yet it still offers to install one.
- The Claude skill describes the new Wake. Install it again from Settings → Claude to get it.
- Needs firmware 0.2.56 for Wake: press **Update** on the screen. The ESP Screens change works right after updating the app. Includes everything from 0.2.64.
## 0.2.64 (firmware 0.2.55)
A long alert title stays on its own line.
+3 -2
View File
@@ -35,5 +35,6 @@ about a second and a half, or put the built-in **Settings** card on a page. What
there appears here within a second, and the other way around.
In Home Assistant every screen also has a **Wake** and a **Sleep** button (firmware 0.2.45 or
newer). Press them from an automation with `button.press`: Wake does what a tap does, Sleep puts
the screen in standby right away, also with Auto standby off.
newer). Press them from an automation with `button.press`: Wake lights the screen up and restarts the
standby time, Sleep puts the screen in standby right away, also with Auto standby off. Wake is not a
touch, so an open card or a later page still goes back to page 1 on time (firmware 0.2.56 or newer).
+2 -2
View File
@@ -257,7 +257,7 @@ Every screen has these entities in Home Assistant, on its ESPHome device. `<scre
| Entity | What it does |
|---|---|
| `button.<screen>_wake` | Wakes the screen the way a tap does and counts the standby time from that moment; a screen that is already on only restarts that count. Firmware {WAKE_SLEEP_MIN_FIRMWARE} or newer. |
| `button.<screen>_wake` | Lights the screen up to its normal brightness and counts the standby time from that moment; a screen that is already on only restarts that count. Firmware {WAKE_SLEEP_MIN_FIRMWARE} or newer. Wake is not a touch: from firmware 0.2.56 an open card or a later page still goes back to page 1 on its own time, and a screen whose time ran out during standby wakes on page 1. |
| `button.<screen>_sleep` | Puts the screen in standby right away, also with Auto standby off, and closes an alert that is showing. It stays in standby until someone taps it, Wake is pressed or an alert arrives. Firmware {WAKE_SLEEP_MIN_FIRMWARE} or newer. |
| `switch.<screen>_auto_standby` | On: the screen dims after the standby time without a touch. Off: the screen wakes up and stays on, except after Sleep, which holds until Wake, a tap or an alert. Firmware {AUTO_STANDBY_MIN_FIRMWARE} or newer. |
| `number.<screen>_standby_after` | Seconds without a touch before standby, 60 to 86400. |
@@ -268,7 +268,7 @@ Every screen has these entities in Home Assistant, on its ESPHome device. `<scre
| `switch.<screen>_night_mode` | Night mode: the night brightness between the two times below. Firmware {SETTING_ENTITIES_MIN_FIRMWARE} or newer, like every row below it. |
| `time.<screen>_night_starts`, `time.<screen>_night_ends` | The night hours; set them with `time.set_value`. |
| `switch.<screen>_24_hour_clock` | On: 24-hour clock. Off: 12-hour clock. |
| `switch.<screen>_back_to_page_1` | On: after `number.<screen>_back_to_page_1_after` seconds without a touch (30 to 3600) the screen closes a card and goes back to page 1. |
| `switch.<screen>_back_to_page_1` | On: after `number.<screen>_back_to_page_1_after` seconds without a touch (30 to 3600) the screen closes a card and goes back to page 1. Pressing Wake, an alert or Auto standby don't count as a touch. |
| `switch.<screen>_back_to_page_1_on_standby` | On: going into standby also goes back to page 1. |
| `switch.<screen>_swipe_between_pages` | On: swipe between pages. |
| `select.<screen>_rotation` | Guition only: `0°`, `90°`, `180°` or `270°`. |
+1 -1
View File
@@ -17,7 +17,7 @@ WEEKDAYS = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']
REPO = 'https://github.com/MaxGramser/homeassistant_espscreen'
REFS = {'cyd': 'main', 'guition': 'main'}
# Firmware shipped with this app release; screens below it get an update offer.
FIRMWARE_VERSION = '0.2.55'
FIRMWARE_VERSION = '0.2.56'
# The Auto standby switch a screen offers Home Assistant automations.
AUTO_STANDBY_MIN_FIRMWARE = '0.2.41'
# The settings page the screen opens itself, and the screen.settings tile that opens it.
+7 -2
View File
@@ -76,6 +76,7 @@ function select(id) {
$("#open-override").disabled = !screen.update?.profile;
$("#editor").hidden = false;
$("#empty").hidden = true;
$("#choose").hidden = true;
$("#dirty").textContent = "All saved";
$("#save-detail").textContent = "Changes apply without reflashing.";
renderScreens();
@@ -494,6 +495,12 @@ function renderPending() {
}
function renderScreens() {
renderPending();
// ESP Screens opens without a chosen screen (app 0.2.65+): the owner picks one. Until then the right side asks
// for that, or offers the install while there are no screens yet.
if (!selected) {
$("#choose").hidden = !inventory.screens.length;
$("#empty").hidden = !!inventory.screens.length;
}
// Keep an open address form alive across the periodic refresh.
if ($("#screens .screen-host")) {
renderUpdates();
@@ -2059,7 +2066,6 @@ async function refresh(full = true) {
if (full) renderClaude();
if (selected) { settleSettings(); renderSettings(); }
if (firstCatalogue && selected && layout && !drag.active && !chipDrag.active) { renderTopbar(); renderTiles(); renderResults(); }
if (!selected && inventory.screens.length) select(inventory.screens[0].id);
if ($("#alerts-dialog").open) renderAlertScreens();
} catch {
$("#connection").textContent =
@@ -2391,7 +2397,6 @@ function applyLive(data) {
$("#connection").classList.toggle("online", inventory.connected);
renderScreens();
if (selected) { settleSettings(); renderSettings(); }
if (!selected && inventory.screens.length) select(inventory.screens[0].id);
}
function listen() {
if (stream || typeof EventSource === "undefined") return;
+7 -1
View File
@@ -42,7 +42,13 @@
<div id="pending"></div>
<button class="quiet" id="refresh">↻ Refresh</button>
</aside>
<section id="empty" class="empty">
<!-- Hidden until the first inventory, so neither card flashes while the page loads. -->
<section id="choose" class="empty" hidden>
<span class="empty-icon">▦</span>
<h2>Choose a screen</h2>
<p>Pick one of your screens to change its tiles, top bar and settings.</p>
</section>
<section id="empty" class="empty" hidden>
<span class="empty-icon">▦</span>
<h2>Start with a screen</h2>
<p>
+8
View File
@@ -735,6 +735,14 @@ dialog label {
#editor {
padding: 18px;
}
/* The screens sit right above it on a phone: a short prompt, not a tall empty card. */
#choose {
min-height: 0;
padding: 18px;
}
#choose .empty-icon {
display: none;
}
.savebar {
align-items: stretch;
flex-direction: column;
+1 -1
View File
@@ -1,5 +1,5 @@
name: ESP Screen Manager
version: "0.2.64"
version: "0.2.65"
slug: esp_screen_manager
description: Choose tiles for your CYD and Guition touchscreen, without reflashing.
url: https://github.com/MaxGramser/homeassistant_espscreen
+33 -10
View File
@@ -1,9 +1,13 @@
"""Opening ESP Screens while the add-on is busy (app 0.2.58).
"""Opening ESP Screens.
The page asks for the full inventory and opens the live stream at the same time. An add-on that is building
firmware answers the stream first, whose light payload has screens but no entities, so the first screen was
opened with an empty catalogue: the entity list said "No entities found" and the tiles showed entity ids until
a filter chip was pressed. The first full inventory now draws those parts again.
App 0.2.58: the page asks for the full inventory and opens the live stream at the same time. An add-on that is
building firmware answers the stream first, whose light payload has screens but no entities, so a screen opened
then had an empty catalogue: the entity list said "No entities found" and the tiles showed entity ids until a
filter chip was pressed. The first full inventory now draws those parts again.
App 0.2.65: the page no longer opens the first screen by itself; the owner picks one. Until then a card asks for
that, or offers the install when there are no screens yet. A screen picked before the full inventory arrives
still gets its entity names from the redraw above.
"""
import re
import unittest
@@ -11,6 +15,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = (ROOT / 'screen_manager/app/static/app.js').read_text()
PAGE = (ROOT / 'screen_manager/app/static/index.html').read_text()
def function(name):
@@ -19,10 +24,28 @@ def function(name):
class Startup(unittest.TestCase):
def test_the_live_stream_can_open_a_screen_before_the_catalogue(self):
# The race exists: the light payload selects the first screen, and it carries no entities.
self.assertIn('if (!selected && inventory.screens.length) select(inventory.screens[0].id);', function('applyLive'))
self.assertRegex(function('select'), r'renderTopbar\(\);\s+renderTiles\(\);\s+renderResults\(\);')
def test_no_screen_opens_by_itself(self):
self.assertNotIn('inventory.screens[0]', SCRIPT)
# The one way into a screen is its button in the list.
self.assertEqual(re.findall(r'(?<![\w.])(?<!function )select\(([^)]*)\)', SCRIPT), ['screen.id'])
for name in ('refresh', 'applyLive'):
self.assertNotRegex(function(name), r'(?<![\w.])select\(', name)
def test_the_right_side_asks_for_a_screen_until_one_is_chosen(self):
choose = re.search(r'<section id="choose" class="empty" hidden>(.*?)</section>', PAGE, re.S)
self.assertTrue(choose, 'hidden until the first inventory')
self.assertIn('<h2>Choose a screen</h2>', choose[1])
self.assertNotIn('<button', choose[1], 'the list beside it is the choice')
self.assertIn('<section id="empty" class="empty" hidden>', PAGE, 'no install card flashing while the page loads')
self.assertIn('<section id="editor" hidden>', PAGE)
body = function('renderScreens')
self.assertIn('if (!selected) {\n $("#choose").hidden = !inventory.screens.length;\n'
' $("#empty").hidden = !!inventory.screens.length;\n }', body)
self.assertLess(body.index('$("#choose").hidden'), body.index('if ($("#screens .screen-host"))'),
'also while an address form keeps the list')
opened = function('select')
for part in ('$("#editor").hidden = false;', '$("#empty").hidden = true;', '$("#choose").hidden = true;'):
self.assertIn(part, opened)
def test_the_first_full_inventory_draws_what_needs_entities_again(self):
body = function('refresh')
@@ -31,7 +54,7 @@ class Startup(unittest.TestCase):
redraw = re.search(r'if \(firstCatalogue && selected && layout && !drag\.active && !chipDrag\.active\) '
r'\{ renderTopbar\(\); renderTiles\(\); renderResults\(\); \}', body)
self.assertTrue(redraw, 'tiles, top bar and entity list are drawn again, never in the middle of a drag')
self.assertLess(redraw.start(), body.index('if (!selected && inventory.screens.length) select('))
self.assertRegex(function('select'), r'renderTopbar\(\);\s+renderTiles\(\);\s+renderResults\(\);')
if __name__ == '__main__':
+40 -5
View File
@@ -1,8 +1,10 @@
"""Wake and Sleep buttons for Home Assistant automations (app 0.2.53, firmware 0.2.45) and the standby
code around them (tidied in app 0.2.54, firmware 0.2.46).
Both board profiles have to wire them the same way: Wake is a tap, Sleep is the standby time running out
right away, and a Sleep holds until a tap, Wake or an alert ends it, whatever turns Auto standby off.
Both board profiles have to wire them the same way: Wake lights the screen and restarts the standby time,
Sleep is the standby time running out right away, and a Sleep holds until a tap, Wake or an alert ends it,
whatever turns Auto standby off. Since app 0.2.65 (firmware 0.2.56) Wake is no tap: Back to page 1 counts
from the last touch, so an automation pressing Wake on every motion no longer holds a card or page 2 up.
"""
import re
import sys
@@ -52,16 +54,49 @@ class Profiles(unittest.TestCase):
self.assertNotIn('entity_category', button, f'{name}: {label} is a control, not a setting')
self.assertNotIn('internal', button, f'{name}: {label} must reach Home Assistant')
def test_wake_is_a_tap(self):
def test_wake_is_light_not_a_tap(self):
for name, text in self.profiles.items():
wake = item(section(text, 'button'), 'name', 'Wake')
# A dimmed screen wakes the way the tap on its wake overlay does; one that is on only counts again.
self.assertRegex(wake, r"return id\(display_dimmed\);'\s+then:\s+- script\.execute: wake_display\s+else:\s+"
# A dimmed screen lights up the way the tap on its wake overlay does, then goes to page 1 at once when
# that time ran out during standby; one that is on only restarts the standby time.
self.assertRegex(wake, r"return id\(display_dimmed\);'\s+then:\s+- script\.execute: wake_display\s+"
r"(?:#[^\n]*\s+)?- script\.execute: back_to_page_1_when_due\s+else:\s+"
r"- lambda: 'id\(last_touch_ms\) = millis\(\);'", name)
self.assertNotIn('last_use_ms', wake, f'{name}: Wake must not postpone Back to page 1')
self.assertNotIn('last_use_ms', script(text, 'wake_display'), f'{name}: waking is no touch')
self.assertIn('id(sleep_requested) = false;', script(text, 'wake_display'), f'{name}: waking ends a Sleep')
overlay = re.search(r'id: dim_wake_overlay\n.*?on_click:\s+- script\.execute: (\w+)', text, re.S)
self.assertEqual(overlay[1], 'wake_display', f'{name}: a tap and Wake share one path')
def test_back_to_page_1_counts_from_the_last_touch(self):
for name, text in self.profiles.items():
clock = item(section(text, 'globals'), 'id', 'last_use_ms')
self.assertIn('type: uint32_t', clock, name)
self.assertIn('restore_value: no', clock, name)
check = script(text, 'back_to_page_1_when_due')
self.assertIn('if (!runtime_tiles::auto_home || id(touch_down) || id(calibration_active) || id(alert_active)) return false;', check, name)
self.assertIn('if (id(display_dimmed)) return false;', check, f'{name}: standby keeps its page ("Also on standby")')
self.assertRegex(check, r'return away && \(millis\(\) - id\(last_use_ms\)\) > \(uint32_t\) runtime_tiles::auto_home_seconds \* 1000UL;'
r'\s+then:\s+- script\.execute: go_home', name)
self.assertNotIn('last_touch_ms', check, f'{name}: the standby clock is not the page clock')
# The one-second interval and Wake run it; nothing else decides about page 1 by itself.
interval = section(text, 'interval')
self.assertIn('- script.execute: back_to_page_1_when_due', interval, name)
self.assertNotIn('auto_home', interval, f'{name}: one copy of the rule')
self.assertEqual(len(re.findall(r'script\.execute: back_to_page_1_when_due', text)), 2, name)
# Written by the boot, the three touchscreen triggers, Home Assistant opening the settings page and the
# UI self test only; Wake, an alert ending and Auto standby restart the standby time alone.
self.assertEqual(len(re.findall(r'id\(last_use_ms\) = millis\(\);', text)), 6, name)
touch = section(text, 'touchscreen')
for trigger in ('on_touch', 'on_update', 'on_release'):
body = re.search(rf'^ {trigger}:\n(.*?)(?=^ [a-z_]+:|\Z)', touch, re.M | re.S)[1]
self.assertIn('id(last_use_ms) = millis();', body, f'{name}: {trigger}')
self.assertIn('id(last_use_ms) = millis();', re.search(r'- action: open_settings\n(.*?)- action:', text, re.S)[1], name)
self.assertIn('id(last_use_ms) = millis();', script(text, 'ui_self_test'), name)
for quiet in (script(text, 'alert_dismiss'), item(section(text, 'switch'), 'name', 'Auto standby')):
self.assertIn('id(last_touch_ms) = millis();', quiet, name)
self.assertNotIn('last_use_ms', quiet, name)
def test_sleep_dims_now_and_holds_with_auto_standby_off(self):
for name, text in self.profiles.items():
sleep = item(section(text, 'button'), 'name', 'Sleep')