Commit Graph

103 Commits

Author SHA1 Message Date
Max Gramser d60a0f122b Show who is at the door: camera images on the Guition (0.2.66)
An alert from esp_screens_show_alert takes a `camera` (a camera.* or image.* entity): a Guition with firmware
0.2.57 shows that moment's picture across the top of the card, and a tap opens the camera full screen. A camera
or image tile opens the same full-screen view, refreshed every four seconds at a steady pace.

ESP Screen Manager fetches the snapshot with its own access, sizes it for the screen and serves a 24-bit BMP
under a random, short-lived link on port 8098; the screen loads it with ESPHome's online_image and holds no
Home Assistant token. BMP because ESPHome decodes it per chunk, where a full-screen JPEG held the loop 0.6 s.
The CYD keeps its alerts without the picture and gets no camera tiles.

The Guition also builds with ESPHome 2026.8+ again: backlight_fade.h derived from ESPHome's LEDC output, which
2026.8 made final, so the backlight now dims through ESPHome's own light transition.

Tested on the bench boards and a local add-on on the Home Assistant Yellow; docs/TEST_RESULTS_0266.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 15:31:11 +02:00
Max Gramser 9567ab591a Record the Dark mode tests: renders, memory and two screens over the air
The light and dark renders of every scene on both boards, the memory of the
LVGL tree before and after, and Dark mode on a CYD and a Guition updated
from ESP Screens and switched from Home Assistant: no restarts, and the
CYD's free heap is the same in both looks as it was on 0.2.51.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:02:20 +02:00
Max Gramser dfd1a26d69 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>
2026-09-17 12:41:19 +02:00
Max Gramser e68162bd86 Keep a long alert title on one line, ending in an ellipsis (0.2.64)
The title label had long_mode: DOT and a width but no height. LVGL 9.5 only
puts the dots on a label whose wrapped text is taller than the label, so the
label grew with its lines and a long title ran into the subtitle. It now has
one line of the headline font as its height (32 px Guition, 21 px CYD).

The Claude skill and the editor's alert tips said the CYD shows about forty
characters of a title; both screens show about twenty.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:17:24 +02:00
Max Gramser bc2a754706 Add Dark mode for a screen beside the bed, with every colour in one table (0.2.63)
Dark mode is a new switch under Brightness on the screen's settings page, in
the Screen settings of ESP Screens and as switch.<screen>_dark_mode in Home
Assistant, so an automation can turn it on at bedtime. The screen changes
look at once, open cards and an alert included, and keeps it after a
restart. The design stays the same: a black page, graphite cards with a
hairline, soft white text, Home Assistant's state colours where they tell
something, and pastel card colours as deeper versions of themselves.

Every colour the firmware chooses now lives in components/smart_display/
theme.h: roles with a light and a dark value, the named card colours and
Home Assistant's state colours. The board profiles give widgets colourless
shared styles that theme.h fills, so a change of look refills those styles
and draws again what code painted by state. The light look is unchanged
apart from merges below one panel step. Dark mode costs 440 bytes of static
RAM on the CYD, and the LVGL tree of the host build is 416 bytes smaller; tests
keep colours out of every other file.

Comparing a screen that switched looks with one that booted in it also
found two older bugs, now fixed: a settings switch that was on showed its
knob at the left whenever its page was drawn, and the sun card's sunlit
area turned orange after a palette change. Firmware 0.2.54.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:07:10 +02:00
Max Gramser 8971ab07e1 Keep the README to the pitch, the pictures and the install, and move the rest to README_EXTENDED
The README had grown into the full documentation. It now opens with a short
why, what and how, shows the screens and ESP Screens in pictures, and keeps
the installation steps. Every card, setting, alert and update detail moves
unchanged to README_EXTENDED.md.

The license now names Max Gramser; the README credits Adrian Kuehlewind's
ESPHome-touch-display-mount, where the first version started. The unused
background images from that project are gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 08:37:57 +02:00
Max Gramser 7311a1cdce Show binary sensors in Home Assistant's words and grey out what is off (0.2.62)
A binary sensor's tile and card now say what Home Assistant says for its
device class, such as Open or Closed for a door and Wet or Dry for a leak
sensor. The screen maps the class it already receives, with the same words
as the top bar and the history card; a test keeps both tables equal. A
history card still waiting for its answer no longer shows the previous
card's word.

Lights and binary sensors that are off turn grey, as in Home Assistant, and
a light without an icon of its own shows a crossed-out bulb while it is off,
on the tile and in the top bar. A chosen icon stays and turns grey.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 08:32:22 +02:00
Max Gramser 8a587303d7 Give each screen its own YAML override that survives updates (0.2.61)
Override YAML on a screen edits <name>.local.yaml, a package loaded after
the shared board package. New profiles get an empty one; an existing
profile is attached on the first save without reformatting the file. The
editor validates YAML, refuses the managed sections and substitutions,
and Save & check runs ESPHome's full validation of the complete profile.

The example uses !extend: ESPHome appends package lists instead of
merging them by id, so a bare id added a second display without a
platform and failed the build. An override is 12 KB at most and a
too-large body answers as JSON instead of a bare 413. The sidecar is
written before the profile includes it, and a symlinked sidecar is
refused. Firmware stays 0.2.52.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 08:23:13 +02:00
Max Gramser 87b2082f81 Colour the sliders of playing media players, covers and numbers (0.2.60)
Since firmware 0.2.42 a slider showed the tile's colour only while
Tile::active() held, which is never true for a playing, paused or idle
media player, a cover or a number, so their volume, position and value
sliders stayed grey like a light that is off.

Tile::slider_active() follows Home Assistant's tile sliders instead: grey
for an unavailable entity, a light or fan that is off and a media player
that is off or in standby; a closed cover keeps its colour as in HA's
cover position feature, and a number with a value is active. The sliders
of direct controls on wide tiles and the small sliders use it;
Tile::active() and its other uses are unchanged.

Checked with 282 Python and 15 C++ tests, generate_packages --check, ESP32
builds of both check profiles and host renders of both boards.
Firmware 0.2.52.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 08:15:56 +02:00
Max Gramser 4f81c7952d Show in the README what tiles can do
Two rows rendered from the firmware with a demo home: heating mode keys,
playback keys and a fan speed slider on double-width tiles, large values,
small sliders and pastel script and scene tiles, a digital clock, a timer and
an off switch on the Guition; the same kinds of tiles and previous/next for a
choice on the CYD.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 23:34:47 +02:00
Max Gramser 86178e941e Keep the history cards together in the README
The temperature history no longer sits between the vacuum card and page 3 while
its siblings have rows of their own: the Guition history row shows the line, the
finger readout and a person's week, the CYD row the power line, readout and the
door. The fan card takes the free place; the Guition door timeline is gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 23:28:51 +02:00
Max Gramser 2bbb8f4e11 Give the history card axes, ranges and a finger readout (0.2.59)
A sensor, number, binary sensor, person or held switch opens a history card:
a line with round axis steps, clock times and the highest and lowest moment
for numbers, a timeline with the time in each state for on/off, zones and
statuses. The screen asks with esphome.screen_history for 1 hour, 24 hours or
a week; the app answers with one compact history message from the recorder's
statistics or the state changes. A finger on the graph reads the value and
time of that moment at the top while the graph stays as it is. The Guition
weather card starts below its round back button, and the README shows the new
cards and the light and weather cards as they look now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 23:20:51 +02:00
Max Gramser d6e5b35dcf Add a card for blinds and curtains, and fix what Studio 1 showed after 0.2.57 (0.2.58)
A tap on a cover now opens a card like Home Assistant's own, in the style
of the climate and vacuum cards: a tall position slider on which the blind
hangs from the top, a tilt slider over slats for venetian blinds, and open,
stop and close keys. The key of the direction the cover moves is filled; a
key that cannot move it further is disabled and stays out of the keys the
card's tick enables. Sliders show their value while dragged and send it on
release. The card offers only what supported_features allows. A
battery-powered blind shows its battery at the top right: the app sends
the device's battery sensor as the extra the vacuum card uses, and adds
current_tilt_position to the attributes.

Checking Studio 1 after its update to firmware 0.2.49 found that a setting
number equal to its initial state 0 (night brightness 0 %) was never
published, so Home Assistant kept it unknown and ESP Screens could not
change it; apply_screen_settings now publishes a number without a state.
The app also took "unavailable" for the device name during the restart
and wrote sensor.esp_screens_unavailable; diagnostics ignore unknown and
unavailable. The editor redraws its tiles, top bar and entity list when
the live stream opened a screen before the first full inventory (a busy
add-on), and loads app.js and style.css by content stamp after Safari kept
an old script.

Checked with 254 Python and 14 C++ tests, generate_packages --check, ESP32
builds of both profiles, and 20 API checks of the card on host builds of
both boards. Firmware 0.2.50.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 16:41:10 +02:00
Max Gramser b58f0cde25 Let the screen own its settings, answer its pings, and show them as cards (0.2.57)
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>
2026-09-16 15:50:41 +02:00
Max Gramser 93fa7e2712 Let a card's back button take its taps again (0.2.56)
Since firmware 0.2.44 the back arrow of the brightness, colour and climate
cards barely responded, and so did the button at the top right of the
climate card. on_boot created the settings hold strip after the YAML cards
on home_page, so the transparent strip lay on top of them and LVGL's hit
test gave it the taps in the top bar region.

attach_hold now takes the first card as `below` and moves the strip and its
fill line to that index, under every card. Holding the top bar opens the
settings page from the tile pages only. The dim wake overlay was never
affected: apply_screen_settings moves it to the foreground when dimmed.

Checked with 215 Python and 14 C++ tests, generate_packages --check, and
ESP32 builds of both Easy profiles. Firmware 0.2.48.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 11:34:21 +02:00
Max Gramser 6df967bfef Keep a slider's value when you let go, and stop needless full resends (0.2.55)
A light's colour slider on Studio 1 sometimes sent red on release. Measured
live: an automation with a state trigger on a six-lamp group (no `to:`) set
"Standby after" to its current value after every colour change. The screen
reported the setting, ESP Screen Manager saved the layout and resent layout,
header and every tile state, and the screen drew its whole page again,
reading no touch input meanwhile. The GT911's stray (0, 0) contact then
became the finger's last position, and LVGL 9.5 set the slider from it on
release. Both red commits left 75-85 ms after such a resync.

The app no longer saves or resends a setting event whose value the layout
already has. The four setting numbers return before the preference write
and the event when nothing changed. A layout message that changes nothing
on the screen no longer runs layout_changed() and refresh_all(); the tile
states after it redraw their own tiles. The Guition wraps the LVGL touch
read callback with cyd::GhostTouch, which repeats the previous read for a
pressed (0, 0), and the edge swipe skips that sample too. Both slider
handlers log a jump to an end on release.

Checked with 214 Python and 14 C++ tests, generate_packages --check, and
ESP32 builds of both Easy profiles. Firmware 0.2.47.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 11:15:40 +02:00
Max Gramser f3af5c2125 Tidy the standby code around Wake and Sleep (0.2.54)
One rule ends a Sleep: a tap, Wake or an alert. The Auto standby switch
cleared sleep_requested when Home Assistant turned standby off, while
ESP Screens and the settings page on the screen did not; now only
wake_display clears the flag, and apply_screen_settings still wakes a
screen the standby time dimmed.

dim_display no longer checks anything itself (the interval checks Auto
standby, the Sleep button sets the flag). It dims, then runs go_home with
"also on standby" on, otherwise closes the settings page and every card
through close_cards. Before, it copied half of go_home and hid the four
YAML overlays itself, so a runtime detail card stayed open under a dimmed
screen. close_cards is now the one runtime_tiles::dismiss() call: the
fallback and the widget.hide actions after its return ran for nothing.

The backlight light becomes internal. Home Assistant could change it, but
the minute tick put the level back, and switching it off darkened the
screen without standby. The brightness numbers and Wake/Sleep replace it.
A dead clock-label branch from the manual profile leaves
apply_screen_settings.

Checked with 209 Python and 14 C++ tests, host builds of both boards over
the native API, and ESP32 builds of both Easy profiles. Firmware 0.2.46.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 10:50:43 +02:00
Max Gramser da20782d3d Wake a screen or put it to sleep from Home Assistant (0.2.53)
Every screen gets two template buttons, Wake and Sleep, for automations
(button.press on button.<screen>_wake / _sleep). Wake does what a tap does:
a dimmed screen runs wake_display, a screen that is on only restarts the
standby count. Sleep dims right away through dim_display, also with Auto
standby off, and closes an alert that is up (reported as remote); it is
ignored during a touch calibration.

A new non-restored flag, sleep_requested, keeps that standby in place:
dim_display dims with standby off, and apply_screen_settings, which runs
every minute, no longer wakes such a screen. wake_display clears it (a tap,
Wake, an alert), and so does the Auto standby switch when it really turns
standby off. Nothing is saved, so the buttons cost no flash writes and may
run on every motion.

The Claude skill, README, add-on docs and RELEASING describe both buttons.
Checked on host builds of both boards over the native API, 24 checks each
(timer, Sleep with Auto standby off across a minute tick, alert,
calibration), and with ESP32 builds of both Easy profiles (+336 B RAM).
Firmware 0.2.45.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 10:35:06 +02:00
Max Gramser b83164d61a 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>
2026-09-16 10:05:53 +02:00
Max Gramser 93c789dc48 Let Claude in Home Assistant put tiles on a screen (0.2.51)
Four Home Assistant events change a screen the way the app's own editor does:
esp_screens_add_tile (add or change), esp_screens_remove_tile,
esp_screens_move_tile and esp_screens_order_tiles. An event names the screen by
device name, shown name, area or title, and may set the size, the direct
control, the display, the icon, the colour and the spot; a tile with a control
or a forecast becomes double-width by itself. apply_tile_event returns the new
layout and Manager.save does the validating and the pushing, so nothing can get
past the twenty-tile limit, a duplicate entity or an occupied spot.

Every event is answered with esp_screens_tile_result, and every screen publishes
what it shows as sensor.esp_screens_<node>, with the page, row, column, width,
control and display per tile. So an assistant can read a screen before it moves
anything, and a refused event says why and changes nothing.

The Claude skill gains a Tiles section with the events, the fields, what each
kind of entity can do, how to read a screen first, and the rule to show the plan
and wait for a yes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 08:19:49 +02:00
Max Gramser 08197c4ae1 Free memory on the CYD and drop the old manual profile (0.2.50)
The CYD restarted under load: a slider fill rounded less than its track made
LVGL draw the fill into a buffer of its own size on every redraw (15-20 KB), and
when that allocation failed the refresh retried until the task watchdog fired.
Track and fill now share one radius, and tests/test_layer_free.py keeps every
layer-making style out of the profiles, the packages and the firmware headers.

A tile keeps only the fields every card uses; climate modes, select options,
weather, sun and timer times, the media title and the vacuum rows moved to an
Extra block that exists only while a state needs one, and the state copy became
a fingerprint. runtime_tiles::model went from 24,296 to 8,216 bytes.

The manual profile that bound tiles to fixed entities in the YAML is gone:
331 TILE* substitutions, DYNAMIC_TILES/TILE_COUNT/DIRECT_ACTIONS, the 80 Home
Assistant subscriptions, its tap handlers and scripts, its own vacuum card, its
guides and helper scripts, and the five stale header copies in the repository
root that two C++ tests were compiling instead of the components. esphome config
of both Easy profiles is unchanged by that cleanup.

tile_scroll and the wake overlay no longer light up on a tap, and the debug
sensor min_free reports the lowest free heap since boot. On the bench CYD static
RAM went from 32.0% to 22.8% and free heap from 64-77 KB to about 101 KB, with
78 KB as the lowest point over thirteen hours without a restart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 08:06:41 +02:00
Max Gramser 79a84a4c77 Run ESP Screen Manager in Docker next to Home Assistant Container (docs only)
For Home Assistant Container users without the App store: docker/compose.yaml
builds the same app image from main and runs it with the existing
SCREEN_DEV settings, so the page only listens on 127.0.0.1 and Home
Assistant (hass_ingress) or an SSH tunnel gives access. docs/DOCKER.md
covers the token, the sidebar panel, USB, backups and updates; the README
links to it. The ESPHome folder mounts at /config because the ESPHome image
keeps its CLI in /esphome, and the healthcheck checks the page instead of
the ESPHome dashboard. No app or firmware change, so no version bump.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 19:01:39 +02:00
Max Gramser 3f124d3bbe Stop the flickering vertical stripes on the CYD panel (0.2.49)
The cheap ILI9341 panel showed a fine vertical line pattern that shimmered
when you moved your eyes or the board. Frame inversion (0xB4 0x07) at the
panel's highest frame rate (0xB1 0x00 0x10) removes it; VCOM stays default.
Firmware 0.2.42 for both boards, version only on the Guition.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 18:29:01 +02:00
Max Gramser dffd6a90c6 Let Home Assistant automations switch Auto standby per screen, and teach the skill (0.2.48)
App 0.2.48 / firmware 0.2.41:
- Both boards expose the switch "Auto standby", the same standby_enabled setting as the checkbox
  in ESP Screens. Off wakes a dimmed screen and keeps it on; on counts the standby time from that
  moment. The change is saved on the screen and reported through esphome.screen_setting, so ESP
  Screens stores it too. No action runs at boot (restore_mode DISABLED).
- The Claude skill gains "Standby and brightness": the switch, Standby after and the three
  brightness numbers, with an example automation that keeps screens awake while someone is home
  and a light is on or a window is open.
- EASY_SETUP and the README describe the entities and the automation.

Tested through Home Assistant's API on both bench boards (CYD: dimmed -> off wakes in 1.1 s, stays
bright past the standby time, on dims again after 61 s); see docs/TEST_RESULTS_0248.md, which also
records a CYD rollback after the first OTA that points to memory headroom, not to this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 18:06:04 +02:00
Max Gramser 57c82fdd12 Bring the big climate card to the Guition, with fan and swing on their own card (0.2.47)
App 0.2.47 / firmware 0.2.40:
- guition-4848s040.yaml gets the CYD's climate card: the setpoint between large -/+ keys
  (debounced, hold to repeat) and one row of mode keys in HA's order and colours; the arc,
  scrim, mode pill and climate_number font are gone.
- Fan and swing modes sit right on the card as segmented rows behind their icons, drawn by the
  new climate_card.h (async rebuild, taps through the touch guard and a profile hook). The card
  is laid out per case from one table: no, one or two settings rows, with or without mode keys.
- Both boards hide a mode key row that would hold a single mode and centre the setpoint instead.
- Glyph arrow-oscillating (Home Assistant's swing icon) joins the icon set.

Verified on the Guition over OTA with the owner's air conditioner and with host renders of both
boards; see docs/TEST_RESULTS_0247.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 15:55:48 +02:00
Max Gramser aa2137f3f8 Choose vacuum, vac & mop or mop on the vacuum card, and a climate card for the CYD (0.2.46)
App 0.2.46 / firmware 0.2.39:
- The vacuum's state message carries extras from its own device: the cleaning mode and water
  selects (found by translation key, then entity id ending, so localized ids work), the suction
  speeds to offer, the battery sensor (HA 2026.8 dropped battery_level), charging and the current
  room. Speeds and water levels a mode covers move into the mode row; Custom only while in use.
  The manager watches these entities and resends only the vacuum's message when one changes.
- Vacuum card redesign on both boards: robot with state, battery, charging bolt and room; Start
  (Pause, Resume) and Dock; then segmented rows for mode, suction and water, showing only the rows
  the chosen mode uses. Taps show at once and wait for Home Assistant; Locate is a round button in
  the Guition hero.
- CYD climate card without the arc: big setpoint between large -/+ keys (debounced, hold to
  repeat) and one row of mode keys in HA's order and colours, with a more key for fan and swing.
- Both profiles clear climate_edit_pending when the climate card is closed, instead of running
  ui_refresh every second while the flag stayed set.

Tested with the owner's Roborock S8 on HA 2026.9.1 (live data, OTA-flashed bench boards) and with
host renders of both boards; see docs/TEST_RESULTS_0246.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 15:22:28 +02:00
Max Gramser 4ff0d44312 Send one alert to every screen, teach Claude about ESP Screens, and add a Settings page (0.2.45)
- The app listens for the Home Assistant events esp_screens_show_alert and
  esp_screens_dismiss_alert and calls each screen's show_alert/dismiss_alert action,
  screens added later included. Fields are typed like Home Assistant's ESPHome action
  schema; an unusable value is left empty instead of losing the alert.
- Settings → Claude installs an ESP Screens skill for Claude Code in Home Assistant
  (/homeassistant/.claude/skills/esp-screens) or downloads it as a zip for claude.ai.
  It is generated from the alert reference, so fields, colors and icons match the firmware.
- New screen, Firmware & USB, firmware updates, Alerts and Claude move to a Settings view;
  the header keeps one button. Unselected screens in My screens get a light grey card.

App only; firmware stays 0.2.38.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 14:52:27 +02:00
Max Gramser 3f1084a171 Drop the duplicate state line on the Guition vacuum card (0.2.44)
Firmware 0.2.38: show_detail() creates no status line for a large vacuum card; the hero badge already shows the state and the two overlapped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 13:37:51 +02:00
Max Gramser 5b50e70472 Second hand on the analog clock, sliders like Home Assistant's, one top bar on every card (0.2.43)
Firmware 0.2.37 for both boards:
- The analog clock card gets a thin red second hand that moves every second while the
  screen is awake (runtime_tiles::screen_awake, set by the profiles from display_dimmed);
  in standby it hides and the clock keeps its once-a-minute redraw.
- Card sliders follow HA's control slider: 12/42 and 8/42 corners, a 4 px handle an
  eighth of the height from the fill end, a stub of a third of the height at 0 or 1 %,
  the track in the fill colour at 20 %. An off light or fan shows only the empty grey
  track, also in the colour card's brightness row. A light's slider floors at 1 %.
- Every overlay card shares one top bar: a round back arrow at the left (60/40 px), the
  name centred, and only the climate power as a right-hand action. The "done"/"ok"
  buttons of the colour and vacuum cards are gone; the brightness card gets its arrow.
- The top bar reads "Connecting to Home Assistant..." / "Waiting for ESP Screens..."
  until the first layout, instead of "Choose tiles in HA".

Checked with the host renderer for both boards and on the Guition over USB.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 13:34:18 +02:00
Max Gramser 0ebb23a1f4 Make page swipes smooth and give lights a new colour card (0.2.42)
Page switches show the new page as empty cards at once and draw two cards per
LVGL refresh, so touch is polled between the steps; on the Guition the longest
loop gap during a swipe dropped from 276 to 74 ms (docs/SWIPE_PROFILE.md).
Custom parts and control panels are reused instead of rebuilt, state updates
and ticks redraw only their own card, clocks redraw once a minute, and a swipe
past the last page draws nothing.

Slider handles sit inside the fill (grey when the card is off), the busy sheet
covers the whole card, and the light colour card is redesigned with rounded
tracks and colour knobs. Firmware 0.2.36; SWIPE_PROFILE builds carry a
per-swipe profiler for measurements.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 10:59:02 +02:00
Max Gramser 8f52b6cb2d Show the screens and ESP Screens in the README; firmware 0.2.35 to test updates (0.2.41)
- README: photos of a Guition at home, both boards with demo layouts, a card gallery,
  the alert with the Alerts cheatsheet, New screen and choosing tiles, tile settings next
  to the resulting tile, and the top bar sheet. Screen images are rendered from the
  firmware's own LVGL code (ESPHome host + SDL) fed by the app's own messages on a demo home.
- App store page (screen_manager/README.md): a photo and the editor via absolute URLs.
- Firmware 0.2.35 only raises SCREEN_FIRMWARE_VERSION so the update path can be tested.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 08:08:38 +02:00
Max Gramser 3e959b3df9 Put ESP Screens in English and keep layouts when entity IDs change (0.2.40)
Everything a user sees or reads is now English: both boards' labels,
status replies and logs, the ESP Screens editor and Alerts cheatsheet,
and the README and guides (docs/TILES.md, CALIBRATING.md, ACCEPTANCE.md
and TROUBLESHOOTING.md replace the Dutch file names). Labels follow
Home Assistant's own English names (Heat/Cool modes, Heating/Cooling
actions, Away, Returning to dock, weather conditions), dates use English
abbreviations and the top bar writes numbers the English way. Docs use
HA 2026.9's menu names (Settings -> Apps, App store, Settings -> Tools).
Unused legacy files (home-like.yaml, buttons.yaml, a.out) are removed.

Firmware 0.2.34 names its entities in English. Home Assistant registers
a renamed ESPHome entity under a new entity ID, so the inbox
text.<screen>_tegelinstellingen becomes text.<screen>_tile_settings and
the layout stored under the old ID would be lost. The manager now
recognises the screen by its device, during a run or after a restart,
and moves its layout and update history along; the Update button and
the nightly round follow the screen through the rename. Screens that
still run 0.2.33 or older keep working: both languages are matched.

Main is the only release branch from now on.

Tested on a Home Assistant Yellow (HA 2026.9.1) with both bench boards:
see docs/TEST_RESULTS_0240.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 02:08:43 +02:00
Max Gramser f60eed3ff6 Make daily traffic scale to dozens of screens: incremental sync, ping keepalive, one action per message (0.2.39)
App 0.2.39 / firmware 0.2.33, items 1, 2, 4, 5 and 6 of the 30-screen scaling check.

- Only the tiles (and top bar) of changed entities are rebuilt; the screen list is
  cached until the registry or a screen diagnostic changes; the SSE stream follows.
- Sensor history comes from one recorder/statistics_during_period call per window in
  a background task, with the REST history as fallback per sensor without statistics.
- Firmware 0.2.33 answers a keepalive ping with its layout revision; a mismatch or a
  restart asks for the layout again, everything is repeated hourly as a safety net.
- The screen_message API action takes one whole message; the text inbox chunks stay
  as fallback for older firmware. A repeated unchanged layout keeps the inbox state.
- Quiet diagnostics: Opgestart (uptime as timestamp) replaces Uptime, heap every
  5 min, setting numbers publish only on change.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 21:06:45 +02:00
Max Gramser 3f6889f750 Let every screen choose its top bar: clocks, date and entities (0.2.38)
The name stays on the left; up to six items go on the right: the time, an
analog clock, the date, or a Home Assistant entity with its icon, showing its
state or how long ago it changed, always or only while active. The add-on
formats entity text in Dutch and sends it as op: header right after the layout
to firmware 0.2.32+; older firmware keeps the clock of show_clock, which now
follows the clock item.

The firmware draws every value in one font on the name's baseline, centres
icons and the dial on the digit height and measures gaps between glyph ink, so
the bar looks even; the editor's mockup uses Roboto and the same integer rules.
tools/render_topbar.py renders the real LVGL code on a computer (ESPHome host +
SDL2). The editor's section buttons are gone.

Firmware 0.2.32 for both boards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:38:49 +02:00
Max Gramser ae308e37ab Add the Alerts cheatsheet to ESP Screens (0.2.37)
Home Assistant shows an ESPHome device action with bare field names and
types only: no description, no dropdowns. The new Alerts button opens a
reference page instead: per screen the exact action names
(esphome.<node>_show_alert and _dismiss_alert) with copy buttons and
whether the firmware is new enough, a paste-ready YAML example per
screen, every field with explanation, example and the byte limits per
board, all icons with glyph and name (searchable, tap copies), the
colour swatches, the behaviour (timeout, button, flash, standby,
replace), and the esphome.screen_alert event with an automation that
waits for the button.

core.alert_reference() carries the data and tests/test_alerts_reference.py
keeps it equal to what both board profiles, tile_palette.h,
alert_overlay.h and tile_icons.py compile. Screens carry alert_action
and dismiss_action in the inventory. App only; firmware stays 0.2.31.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 15:08:40 +02:00
Max Gramser e37148f9b1 Show an alert from a Home Assistant automation (0.2.36)
Every screen gets the user action esphome.<device>_show_alert with title,
subtitle, icon, color, button_text, timeout and flash. The card sits on
LVGL's top layer above every page, card and the dim overlay, wakes the
screen and keeps the backlight at its normal brightness until the button
(or the timeout; 0 waits for the button, the button always closes at
once). flash: true blinks the backlight four times on arrival. Icons are
the tile picker names (also mdi:name or the hex codepoint of a compiled
glyph, else alert-outline), colours the tile pastels. A new alert replaces
the current one; every ending fires esphome.screen_alert with action,
title and screen. dismiss_alert removes the card remotely.

The field is button_text rather than button: ESPHome reserves the names
of loaded components as ids and the CYD profile loads button:. The C++
namespace is screen_alert because the widget id alert_overlay becomes a
global lv_obj_t*.

tools/generate_icons.py now also writes tile_icon_names.h (name to
codepoint) from the one icon list. Firmware 0.2.31 for both boards.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 14:39:03 +02:00
Max Gramser 61ceb40075 Keep the empty edge band out of LVGL so a quick swipe gets its samples (0.2.35)
On 0.2.29 the edge swipe flipped pages, but about half of the quick flicks
on Studio 1 ended with "randveeg niet gevuurd: 5 px naar binnen": every
touch lasted about 160 ms and produced a single sample. A press in the
empty band landed on tile_scroll or the page, and the theme gives every
object a pressed style, so LVGL repainted nearly the whole screen on press
and on release, blocking the loop and the 20 ms touch polling for as long
as the flick took. The page and the tile container are no longer
clickable: no state change, no repaint, no events, and the edge swipe on
the touchscreen triggers does not need them. While an edge swipe is armed
every sample is logged, and the press line carries the contact id, so the
sampling can be read from the log. Firmware 0.2.30 for both boards.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 14:02:47 +02:00
Max Gramser 7564e612bb Feed the edge swipe from the touchscreen triggers again (0.2.34)
The 0.2.32 edge swipe listened on LVGL's input device, but the LVGL 9.5.0
bundled by ESPHome sends the indev PRESSED and RELEASED without PRESSING,
so the swipe never received a position update: on Studio 1 the log showed
the GT911 press and then nothing. The Guition follows the swipe through
on_touch, on_update and on_release again, the route that worked live in
0.2.24 once the card-close fix was in, with ESPHome's own rotation mapping,
the 45-degree rule, end() on release so a stale armed state cannot fire on
the next touch, and the "genegeerd"/"niet gevuurd" log lines. Firmware
0.2.29 for both boards; the app only moves its target version.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 13:40:36 +02:00
Max Gramser e3efbbaa06 Let LVGL decide a tap on the Guition: release inside the tile counts (0.2.33)
A firm press on Studio 1 that drifted 73 px was still refused by the touch
guard's movement limit. On a capacitive panel that limit has no job left:
the edge swipe consumes its own tap, sliders capture their own drag, and
LVGL already drops the click when the finger leaves the tile. The guard's
limit can now be 0 (no limit); the Guition profile sets it so, the CYD
keeps 56 px because its resistive panel jumps. Firmware 0.2.28 for both
boards; the app only moves its target version.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 13:15:09 +02:00
Max Gramser b9e1dfbd95 Close cards through one path so the page swipe is never left blocked (0.2.32)
Read live on a Guition: every edge swipe was recognised but refused by the
"card open" check, because the light, climate and vacuum cards cleared
their overlay on close (X, tap beside the card, wake from standby) without
forgetting active_entity. One opened card blocked swiping until the next
layout change; the old whole-screen gesture and the CYD had the same gate.
Both boards now close cards through one script, close_cards, which hides
every overlay and clears that state; wake_display uses it too.

The Guition edge swipe now follows LVGL's own pointer events
(lv_indev_add_event_cb: PRESSED begins, PRESSING flips the page and calls
lv_indev_wait_release from inside LVGL) instead of ESPHome's touchscreen
on_update, so the point arrives already rotated. The direction rule is 45
degrees instead of twice as horizontal, and the log now says why a swipe
was refused or how far an unfinished one got. Firmware 0.2.27 for both
boards; the app only moves its target version.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 13:09:53 +02:00
Max Gramser 95cdfa30cf Free drag & drop with fixed positions, empty cells and kept pages (0.2.31)
Tiles get an absolute grid slot instead of a packed order: a tile lands exactly
where it is dropped, dropping on another tile swaps the two, everything else
stays put, and empty cells and empty pages persist. The mockup previews the
result while dragging, a new page waits below the last one, "Pagina toevoegen"
keeps an empty page, empty cells accept the next tile from the picker, and the
mouse no longer selects text while dragging.

Storage and protocol stay version 1: `tiles[].slot` (0-47) and `pages` (1-8)
are additive, the layout message carries `slots` and `pages`. Firmware 0.2.26
places tiles on those slots (`place()` next to `pack()`); older firmware packs
in order as before and the editor says so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 13:00:39 +02:00
Max Gramser ad0385d60f Shorten the page intro to one heading
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 12:25:39 +02:00
Max Gramser 6cdd6468c5 Drop the firmware hint, the how-it-works section and its Uitleg button
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 12:25:21 +02:00
Max Gramser 5d24a770a4 Drop the how-to cards and two long hints from the editor page
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 12:24:06 +02:00
Max Gramser 0120175dce Show a green dot for online screens in the screen list (0.2.30)
The status line under each screen name used the muted text colour for the
online dot, so online and offline screens looked alike. The dot is now its
own span and turns green when the screen is online; offline keeps the grey
open circle. Firmware 0.2.25 for both boards carries only the new version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 11:50:56 +02:00
Max Gramser 104f20e4fa Swipe in from the side edge to change pages on the Guition (0.2.29)
Page swipes were an LVGL gesture anywhere on the screen: 50 px, fast only,
and almost always starting on a tile that lit up as pressed. On the Guition
a touch that starts within 32 px of the left or right edge and travels
40 px inward, clearly sideways, now flips one page (from the right edge
leftwards is next, from the left edge rightwards previous), at any speed
and in every rotation; a swipe that starts in the middle does nothing, so
taps and drags on tiles can no longer change the page by accident. The
whole-screen gesture is removed from the Guition profile; the CYD keeps
it. Firmware 0.2.24 for both boards, the app only reworded the setting.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 11:40:19 +02:00
Max Gramser ea0b423fab Let a tap survive a centimetre of finger drift and log every refused tap (0.2.28)
The touch guard dropped a tap as soon as the finger drifted more than
18 px (under 3 mm) from its very first contact point, on both boards and
regardless of the page-swipe setting; LVGL itself never cancelled these
clicks (no scrollable ancestors, gestures do not suppress clicks). A finger
that flattens or rolls while pressing was enough, and the tap vanished
without a trace in the log.

The limit is now about one centimetre per board (TOUCH_MOVE_LIMIT_PX:
Guition 67 px, CYD 56 px), measured from a reference that settles over the
first four samples; only the contact that started the touch is followed,
so a second finger no longer counts as movement; the minimum contact time
is per board too (TOUCH_MIN_PRESS_MS: 60 ms resistive, 20 ms capacitive).
Every refused tap is logged on INFO with its reason (moved, too short,
already used, bounce). Firmware 0.2.23 for CYD and Guition.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 11:30:23 +02:00
Max Gramser 2327d31261 One-window install: profile, wifi, USB flash and pairing hints (0.2.27)
"Nieuw scherm" used to span two dialogs plus a copy/download of the
installation YAML. It is now one dialog: board, name (node name derived,
editable), wifi only when secrets.yaml lacks it, the detected USB port,
and Installeren. The add-on writes the profile with unique API/OTA keys,
merges just the missing wifi lines into secrets.yaml (comments and other
secrets untouched, result re-parsed before writing), then builds and
flashes with the live ESPHome log and stage in the same dialog; a failed
build offers a retry without a second profile. The YAML download
endpoint is gone: the profile already lives in the ESPHome folder.

Pairing happens in Home Assistant itself, so the done view and a sidebar
card per not-yet-paired profile say so, with a button to Apparaten &
diensten, the API key to copy and the reminder to allow HA actions.
One profile per screen; the dialog says that too.

Tests: wifi merge cases, install pre-checks, pending list, profile
metadata; the date-anchored hourly forecast test now follows the clock.
Firmware 0.2.22 stays current.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 11:11:23 +02:00
Max Gramser 5d6651d68b Watch the feed by the declared keepalive and HA by the API link (0.2.26)
Since 0.2.20 the manager repeats a layout every 120 s, while the firmware
treated anything older than 95 s (sized for the original 25 s keepalive) as
stale: on a quiet screen every card read "Niet beschikbaar" and the header
"HA niet verbonden" for 25-45 s of each two-minute round, on both boards.

"HA niet verbonden" now follows ESPHome's own api_is_connected(); the feed
watchdog uses the cadence the layout message declares ("keepalive", seconds,
120 s when absent) and tolerates one missed round plus loop slack before
showing "ESP Screens niet actief". The tick redraws once when either flips.
Firmware 0.2.22 for CYD and Guition.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 15:45:17 +02:00
Max Gramser ea7bf3387b Rapid -/+ taps and hold-to-repeat on the setpoint pill (0.2.25)
App 0.2.25 / firmware 0.2.21. The 600 ms touch-guard lockout dropped a second
tap on the same -/+ key; stepper keys now use a 150 ms bounce guard, repeat
while held (3 per second), and still send one call after 700 ms of rest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 15:20:38 +02:00