Files
homeassistant_espscreen/screen_manager/app/static/index.html
T
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

548 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>ESP Screens</title>
<link rel="stylesheet" href="static/style.css" />
<script src="static/app.js" defer></script>
</head>
<body>
<header>
<div class="brand">
<span class="mark">▦</span>
<div>
<strong>ESP Screens</strong
><small>Tiles for your CYD and Guition, without reflashing</small>
</div>
</div>
<nav class="toolbar" aria-label="Actions">
<button id="open-settings" class="tool" type="button" aria-controls="settings-view">
<span class="tool-icon" id="open-settings-icon" aria-hidden="true">⚙</span>
<span class="tool-text"
><b id="open-settings-label">Settings</b
><small id="open-settings-hint">New screen, updates, alerts, Claude</small></span
>
</button>
</nav>
</header>
<main>
<div id="home-view">
<div class="intro">
<div>
<p class="eyebrow">HOME ASSISTANT</p>
<h1>Manage your remote screens here.</h1>
</div>
<span id="connection" role="status" class="badge">Connecting…</span>
</div>
<div class="workspace">
<aside>
<h2>My screens</h2>
<div id="screens"></div>
<div id="pending"></div>
<button class="quiet" id="refresh">↻ Refresh</button>
</aside>
<section id="empty" class="empty">
<span class="empty-icon">▦</span>
<h2>Start with a screen</h2>
<p>
Connect a CYD or Guition via USB to your Home Assistant machine.
Profile, firmware, and the first flash happen in one window.
</p>
<button id="start">Install new screen</button>
</section>
<section id="editor" hidden>
<div class="editor-head">
<div>
<p class="eyebrow" id="screen-name"></p>
<h2>Make it your screen</h2>
</div>
<span id="delivery" class="badge"></span>
</div>
<section class="topbar-editor" id="topbar" aria-labelledby="topbar-title">
<div class="section-title">
<h3 id="topbar-title">Top bar</h3>
<span id="topbar-count"></span>
</div>
<div class="topbar-fields">
<label class="topbar-name"
>Left: name<input
id="title"
maxlength="60"
placeholder="For example: Living room"
/></label>
<div class="topbar-right">
<span class="topbar-caption" id="topbar-caption">Right: time, sensors, or a clock</span>
<div
id="topbar-chips"
class="topbar-chips"
role="list"
aria-labelledby="topbar-caption"
></div>
</div>
</div>
<p class="hint" id="topbar-hint"></p>
</section>
<div class="columns" id="tile-section">
<div class="tile-editor">
<div class="section-title">
<h3>Your tiles</h3>
<span id="count">0 / 10</span>
</div>
<div id="layout-preview" aria-label="Screen layout"></div>
<button id="add-page" class="quiet add-page" type="button">
+ Add page
</button>
<p id="no-tiles" class="hint">
Add your first light, scene, or device via the picker.
</p>
<p id="positions-hint" class="hint" hidden></p>
</div>
<div class="picker">
<label for="search">Add an entity</label
><input
id="search"
type="search"
placeholder="Search by name, device, or room…"
autocomplete="off"
/>
<div class="filters" id="filters"></div>
<div id="results" aria-live="polite"></div>
</div>
</div>
<section class="screen-settings" id="general-settings" aria-labelledby="settings-heading">
<div class="section-title settings-title">
<h3 id="settings-heading">Screen settings</h3>
<span class="hint" id="settings-status" role="status"></span>
</div>
<div class="settings-cards" id="settings-groups"></div>
</section>
<details class="screen-settings" id="inspector-section">
<summary>
Inspector
<span class="hint"
>Connection, status, and supported properties</span
>
</summary>
<button id="inspect" class="quiet">Read current data</button>
<div id="inspection-summary"></div>
<pre id="inspection">Select a screen and read the data.</pre>
</details>
<footer class="savebar">
<div>
<strong id="dirty">All saved</strong
><small id="save-detail"
>Changes apply without reflashing.</small
>
</div>
<button id="save">Save & send to screen</button>
</footer>
</section>
</div>
</div>
<section id="settings-view" class="settings-view" hidden aria-labelledby="settings-title">
<div class="intro">
<div>
<p class="eyebrow">SETTINGS</p>
<h1 id="settings-title">Everything around your screens.</h1>
</div>
<button type="button" class="quiet" id="close-settings">← My screens</button>
</div>
<div class="settings-groups">
<section class="settings-group" aria-labelledby="settings-screens-title">
<h2 id="settings-screens-title">Screens</h2>
<p class="hint">Add a screen, or build and reinstall the firmware of one you already have.</p>
<div class="settings-tools">
<button id="new-screen" class="tool tool-primary" type="button">
<span class="tool-icon" aria-hidden="true">+</span>
<span class="tool-text"
><b>New screen</b><small>Connect and install</small></span
>
</button>
<button id="open-firmware" class="tool" type="button">
<span class="tool-icon" aria-hidden="true">⇪</span>
<span class="tool-text"
><b>Firmware & USB</b
><small>Build and install via USB or Wi-Fi</small></span
>
</button>
</div>
</section>
<section class="settings-group updates" id="updates" aria-labelledby="updates-title">
<h2 id="updates-title">Firmware updates</h2>
<p class="hint" id="updates-hint"></p>
<button id="update-all" hidden>Update all screens</button>
<label class="check">
<input type="checkbox" id="auto-update" />
<span
>Update automatically every night<small
>Between 03:00 and 06:00, one screen at a time. Stops on an
error and reports it in Home Assistant.</small
></span
>
</label>
</section>
<section class="settings-group" aria-labelledby="settings-alerts-title">
<h2 id="settings-alerts-title">Alerts</h2>
<p class="hint">
A card over the whole screen from an automation, on one screen or on all of them.
The cheatsheet has the action names, fields, icons, and colors.
</p>
<div class="settings-tools">
<button id="open-alerts" class="tool" type="button">
<span class="tool-icon" aria-hidden="true">!</span>
<span class="tool-text"
><b>Alerts</b
><small>Full-screen notifications, from an automation</small></span
>
</button>
</div>
</section>
<section class="settings-group" id="claude" aria-labelledby="claude-title">
<h2 id="claude-title">Claude</h2>
<p class="hint">
Teach Claude about ESP Screens, then ask: "Show an alert on all my screens when the
mailbox is full." Claude knows the alert events, every field, color, and icon.
</p>
<div class="claude-skill">
<button type="button" id="claude-install">Install for Claude Code</button>
<span id="claude-status" class="hint" role="status"></span>
</div>
<small>
For Claude Code in Home Assistant. Writes one file to
<code id="claude-path">/homeassistant/.claude/skills/esp-screens</code>; installing
again replaces it.
</small>
<div class="claude-skill">
<a class="button again" id="claude-download" href="api/claude-skill.zip" download="esp-screens.zip">Download for claude.ai</a>
<small>Upload the zip in Claude under Customize → Skills.</small>
</div>
</section>
</div>
</section>
</main>
<dialog id="installer">
<form id="install-form">
<div class="dialog-head">
<div>
<p class="eyebrow">NEW SCREEN</p>
<h2 id="install-title">Connect and install.</h2>
</div>
<button
type="button"
class="quiet"
id="close-install"
aria-label="Close"
>
✕
</button>
</div>
<div id="install-setup">
<p>
Connect the screen with a USB data cable to the machine running Home
Assistant. ESP Screens creates its own profile with unique
keys, builds the firmware, and puts it straight on the screen.
</p>
<fieldset>
<legend>Which screen do you have?</legend>
<label class="board"
><input type="radio" name="board" value="cyd" checked /><span
><b>CYD · 2.8 inch</b
><small>ESP32-2432S028 · 320 × 240</small></span
></label
><label class="board"
><input type="radio" name="board" value="guition" /><span
><b>Guition · 4 inch</b
><small>ESP32-S3-4848S040 · 480 × 480 · GT911</small></span
></label
>
</fieldset>
<label
>Name<input
name="friendly_name"
required
maxlength="60"
placeholder="Living room screen"
autocomplete="off" /><small class="node-line"
>Device name <code id="node-preview">screen</code
><button type="button" class="link" id="edit-node">
customize
</button></small
><small
>Every screen gets its own profile with its own keys. Four
screens? Go through this four times with a different name.</small
></label
>
<label id="node-label" hidden
>Device name<input
name="name"
pattern="[a-z][a-z0-9-]{0,29}"
maxlength="30"
autocomplete="off" /><small
>Lowercase letters, digits, and dashes. A different name for
each screen.</small
></label
>
<fieldset id="wifi-fields" class="wifi" hidden disabled>
<legend>Wi-Fi</legend>
<p class="hint" id="wifi-status"></p>
<label id="wifi-ssid-label"
>Wi-Fi name (2.4 GHz)<input name="wifi_ssid" autocomplete="off"
/></label>
<label id="wifi-password-label"
>Wi-Fi password<input
name="wifi_password"
type="password"
autocomplete="new-password"
/></label>
</fieldset>
<label
>Install via<select name="target" id="install-target"></select
><small id="target-hint"></small
></label>
<p class="hint" id="install-note"></p>
<div class="install-actions">
<button type="submit" id="install-go">Install</button>
<span id="install-status" role="status"></span>
</div>
</div>
<div id="install-progress" hidden>
<div class="progress-head">
<span class="spin" id="progress-spin"></span>
<span class="outcome" id="progress-mark" hidden></span>
<strong id="progress-title"></strong>
</div>
<p id="progress-detail"></p>
<div id="install-result" hidden>
<div class="key-box">
<span>API key</span><code id="api-key"></code>
<button type="button" class="mini" id="copy-key">Copy</button>
</div>
<ol class="steps" id="install-steps"></ol>
</div>
<details id="install-log-wrap">
<summary>ESPHome log</summary>
<pre id="install-log"></pre>
</details>
<div class="install-actions">
<button type="button" id="install-retry" hidden>
Retry
</button>
<button type="button" class="quiet" id="install-close">
Close
</button>
</div>
</div>
</form>
</dialog>
<dialog id="firmware-dialog">
<div class="dialog-head">
<div>
<p class="eyebrow">FIRMWARE & USB</p>
<h2>Build and install screen firmware</h2>
</div>
<button id="close-firmware" class="quiet" aria-label="Close">✕</button>
</div>
<p>
The built-in ESPHome CLI uses your own profile (YAML with keys)
and secrets. Choose the screen's profile, then a USB port (screen
connected to the Home Assistant machine) or <b>Wi-Fi / OTA</b> with the hostname.
<b>Check</b> only validates, <b>Build only</b> compiles,
<b>Build & install</b> puts the firmware on the screen. Your tiles
stay as they are in ESP Screens. The first build takes a few minutes.
</p>
<label>ESPHome profile<select id="firmware-file"></select></label>
<label
>Install to<select id="firmware-port">
<option value="ota">Wi-Fi / OTA</option>
</select></label
>
<label id="firmware-host-label"
>IP address or hostname<input
id="firmware-host"
placeholder="screen-livingroom.local"
/></label>
<div class="yaml-actions">
<button id="firmware-validate" class="quiet">Check</button
><button id="firmware-build" class="quiet">Build only</button
><button id="firmware-install">Build & install</button>
</div>
<p id="firmware-status" role="status"></p>
<pre id="firmware-log"></pre>
</dialog>
<dialog id="tile-sheet" class="tile-sheet" aria-label="Configure tile"></dialog>
<dialog id="topbar-sheet" class="tile-sheet" aria-label="Top bar"></dialog>
<dialog id="alerts-dialog" class="alerts" aria-label="Alerts">
<div class="dialog-head">
<div>
<p class="eyebrow">ALERTS · CHEATSHEET</p>
<h2>A full-screen notification, from an automation.</h2>
</div>
<button id="close-alerts" class="quiet" aria-label="Close">✕</button>
</div>
<p>
Every screen has the <code>show_alert</code> action. It places a card over everything,
wakes the screen, and keeps the backlight at normal brightness until someone taps
the button or the timeout runs out. Home Assistant itself shows no explanation or
dropdowns for ESPHome actions; everything you can fill in is listed here.
</p>
<div class="alert-hero">
<div class="alert-mock" id="alert-mock" aria-hidden="true">
<div class="alert-mock-card">
<span class="mdi alert-mock-icon" id="alert-mock-icon"></span>
<strong id="alert-mock-title">Someone is at the door</strong>
<span id="alert-mock-subtitle">Door 3, back</span>
<button type="button" id="alert-mock-button" tabindex="-1">Coming</button>
</div>
</div>
<nav class="alerts-nav" aria-label="Sections">
<button type="button" data-jump="alerts-screens">Your screens</button>
<button type="button" data-jump="alerts-howto">In an automation</button>
<button type="button" data-jump="alerts-all">All screens</button>
<button type="button" data-jump="alerts-fields">The fields</button>
<button type="button" data-jump="alerts-icons">Icons</button>
<button type="button" data-jump="alerts-colors">Colors</button>
<button type="button" data-jump="alerts-behaviour">Behavior</button>
<button type="button" data-jump="alerts-events">Events</button>
<button type="button" data-jump="alerts-tips">Tips</button>
</nav>
</div>
<section id="alerts-screens">
<h3>Your screens</h3>
<p>
Every screen has its own action, with the device name in it. From firmware
<code id="alerts-min-firmware">0.2.31</code>; update an older screen with its
Update button under My screens.
</p>
<div id="alerts-screen-list"></div>
</section>
<section id="alerts-howto">
<h3>In an automation</h3>
<ol class="steps">
<li><b>Add action</b> and search for <b>ESPHome</b>. Choose your screen's action:
Home Assistant calls it "Performs the action show_alert of the node
<i>device name</i>".</li>
<li><b>Fill in the fields.</b> Home Assistant asks for all seven; leave what you don't
use empty (<code>""</code>, <code>0</code>, off).</li>
<li>Prefer YAML? Choose <b>Edit in YAML</b> on the action and paste the example
below.</li>
</ol>
<div class="alert-example">
<label
>Example for<select id="alerts-example-screen"></select
></label>
<div class="copy-line">
<pre id="alerts-example"></pre>
<button type="button" class="mini" id="alerts-example-copy">Copy YAML</button>
</div>
</div>
</section>
<section id="alerts-all">
<h3>All screens at once</h3>
<p>
Fire the event <code id="alerts-all-event">esp_screens_show_alert</code> with the same
fields, and ESP Screens passes the alert on to every screen that is online, including
screens you add later. <code id="alerts-all-dismiss">esp_screens_dismiss_alert</code>
clears it everywhere. In an automation, add the <b>Event</b> action or paste the example
under <b>Edit in YAML</b>. Leave out the fields you don't use. ESP Screens has to be
running for this.
</p>
<div class="copy-line">
<pre id="alerts-all-example"></pre>
<button type="button" class="mini" id="alerts-all-copy">Copy YAML</button>
</div>
</section>
<section id="alerts-fields">
<h3>The fields</h3>
<p>
Text is free-form; the limit is in bytes, and an accented letter counts as two.
What doesn't fit gets an ellipsis (title) or is dropped (subtitle).
</p>
<div class="table-scroll"><table id="alerts-field-table"></table></div>
</section>
<section id="alerts-icons">
<h3>Icons</h3>
<p>
Fill in the name in <code>icon</code>, for example <code>doorbell</code>.
<code>mdi:doorbell</code> and the hex codepoint (<code>F12E6</code>) also work.
Only these icons are included in the firmware; any other name shows the
warning triangle. Tap an icon to copy its name.
</p>
<p class="alerts-subhead">Handy for notifications</p>
<div class="chips" id="alerts-suggested"></div>
<label class="alerts-search"
>Search for an icon<input
id="alerts-icon-search"
type="search"
placeholder="Search by name or description…"
autocomplete="off"
/></label>
<div id="alerts-icon-groups"></div>
</section>
<section id="alerts-colors">
<h3>Colors</h3>
<p>
Fill in the name in <code>color</code>. These are the pastel shades of the tiles,
with dark text. Empty or unknown gives the white card. Tap to copy.
</p>
<div class="swatches" id="alerts-swatches"></div>
</section>
<section id="alerts-behaviour">
<h3>Behavior</h3>
<ul class="alerts-list">
<li><b>Wakes the screen.</b> An alert immediately wakes a dimmed or dark screen and
appears above every page, every open light card, and the standby layer.</li>
<li><b>On until the button.</b> As long as the card is showing, the backlight stays at
normal brightness. Standby and night hours wait. After the button, the regular
standby time starts again.</li>
<li><b>Timeout.</b> <code>timeout: 0</code> waits for the button, however long that
takes. With <code>timeout: 60</code> the card disappears after a minute, but the
button closes it sooner.</li>
<li><b>Blinking.</b> <code>flash: true</code> blinks the backlight four times
when the alert arrives (about a second) and leaves the screen on afterward.</li>
<li><b>Replacing.</b> A new alert replaces the current one; there's no stack.
The replaced alert reports itself as <code>replaced</code>.</li>
<li><b>Closing remotely.</b> <code>dismiss_alert</code> clears the card,
for example if the door is already open. Without an alert, that action does nothing.</li>
<li><b>Tapping beside the card</b> does nothing; only the button closes it. That way a
wake-up tap never accidentally dismisses the alert.</li>
</ul>
</section>
<section id="alerts-events">
<h3>Events</h3>
<p>
Every end fires the event <code id="alerts-event-name">esphome.screen_alert</code>
with <code>action</code>, <code>title</code>, <code>screen</code> (the device name)
and the <code>device_id</code> that Home Assistant adds. Visible under
Settings → Tools → Events.
</p>
<div class="table-scroll"><table id="alerts-ending-table"></table></div>
<p class="alerts-subhead">Waiting until someone presses the button</p>
<div class="copy-line">
<pre id="alerts-wait-example"></pre>
<button type="button" class="mini" id="alerts-wait-copy">Copy YAML</button>
</div>
</section>
<section id="alerts-tips">
<h3>Tips</h3>
<ul class="alerts-list">
<li>Use a <b>color per kind of notification</b>: orange for the door, red for
smoke or water, green for "done". The text always stays dark and readable.</li>
<li>Keep the title <b>short</b>; the CYD shows about forty characters on one line.
Put details in the subtitle.</li>
<li>A <b>custom button text</b> turns the notification into a question: "Coming", "Seen",
"Turn off". The event then tells you who responded.</li>
<li>For an alert that resolves itself (the door opens, the laundry gets taken out)
close it in the same automation with <code>dismiss_alert</code>.</li>
<li>Every screen? Fire <code>esp_screens_show_alert</code> once (see All screens at
once). The screens still work independently and each reports with its own
<code>screen</code>.</li>
<li>Rather <b>ask Claude</b> for the automation? Install the ESP Screens skill under
Settings → Claude.</li>
</ul>
</section>
</dialog>
<div id="toast" role="status" hidden></div>
</body>
</html>