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>
This commit is contained in:
@@ -14,7 +14,8 @@ import zipfile
|
||||
import tile_icons
|
||||
from core import (ALERT_ENDINGS, ALERT_EVENT, ALERT_FALLBACK_ICON, ALERT_FIELDS, ALERT_LIMITS, ALERT_MAX_TIMEOUT,
|
||||
ALERT_MIN_FIRMWARE, ALERT_SUGGESTED_ICONS, AUTO_STANDBY_MIN_FIRMWARE, BROADCAST_DISMISS, BROADCAST_SHOW,
|
||||
CONTROLS, DISPLAYS, MAX_PAGES, SLOTS_PER_PAGE, TILE_BACKGROUNDS, TILE_EVENTS, TILE_RESULT_EVENT)
|
||||
CONTROLS, DISPLAYS, MAX_PAGES, SETTINGS_PAGE_MIN_FIRMWARE, SLOTS_PER_PAGE, TILE_BACKGROUNDS,
|
||||
TILE_EVENTS, TILE_RESULT_EVENT)
|
||||
|
||||
NAME = 'esp-screens'
|
||||
# claude.ai accepts at most 200 characters; Claude Code picks the skill by this sentence.
|
||||
@@ -298,6 +299,23 @@ actions:
|
||||
```
|
||||
|
||||
Replace the person, light, window and screen with the real entity IDs (ask which screens when there are several), and leave out conditions the user did not ask for.
|
||||
|
||||
## The settings page on the screen itself
|
||||
|
||||
Firmware {SETTINGS_PAGE_MIN_FIRMWARE} or newer carries a settings page the user can open at the panel: hold the top bar (the strip with the screen's name and the clock) for about a second and a half, until the blue line along the top edge is full. A screen can also carry a tile for it: put the entity `screen.settings` on a screen like any other tile.
|
||||
|
||||
It holds Brightness, Night, Screen (clock, going back to page 1, swiping, rotation) and This screen (name, address, firmware, whether Home Assistant is connected, and Restart). A change there is saved on the screen and shows up in ESP Screens within a second, exactly like a change made from Home Assistant.
|
||||
|
||||
You can open it for someone who is standing at the panel:
|
||||
|
||||
```yaml
|
||||
actions:
|
||||
- action: esphome.kitchen_screen_open_settings
|
||||
data:
|
||||
page: 1
|
||||
```
|
||||
|
||||
`page` 0 is the menu, 1 Brightness, 2 Night, 3 Screen, 4 This screen, and -1 closes the page and puts the screen back on page 1. Use it to walk someone through a setting ("I opened Brightness on the kitchen screen"), not to change something yourself: for that, use the entities above, which do the same thing without anyone at the panel.
|
||||
'''
|
||||
|
||||
def status(directory):
|
||||
|
||||
@@ -11,15 +11,17 @@ import tile_icons
|
||||
|
||||
DOMAINS = frozenset('light switch input_boolean scene script climate vacuum fan cover sensor binary_sensor input_select select number input_number weather media_player button input_button sun timer person screen'.split())
|
||||
# Built-in cards without a Home Assistant entity; firmware 0.2.14+ renders them.
|
||||
BUILTIN = {'screen.clock': 'Clock'}
|
||||
BUILTIN = {'screen.clock': 'Clock', 'screen.settings': 'Settings'}
|
||||
NEW_DOMAINS = frozenset('sun timer person screen'.split())
|
||||
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.43'
|
||||
FIRMWARE_VERSION = '0.2.44'
|
||||
# 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.
|
||||
SETTINGS_PAGE_MIN_FIRMWARE = '0.2.44'
|
||||
ATTRS = frozenset('brightness percentage current_position current_temperature temperature current_humidity min_temp max_temp target_temp_step supported_color_modes hvac_modes hvac_action hs_color color_temp_kelvin min_color_temp_kelvin max_color_temp_kelvin fan_speed_list unit_of_measurement battery_level fan_speed volume_level is_volume_muted media_title options min max step temperature_unit supported_features device_class next_rising next_setting finishes_at duration remaining humidity wind_speed wind_speed_unit apparent_temperature fan_modes swing_modes fan_mode swing_mode'.split())
|
||||
# Attributes whose boolean value the screen needs; every other bool stays behind.
|
||||
BOOL_ATTRS = frozenset(['is_volume_muted'])
|
||||
@@ -165,7 +167,16 @@ SETTING_RULES = {
|
||||
'home_on_standby': (False, None, None),
|
||||
'swipe_pages': (False, None, None),
|
||||
'rotation': (0, 0, 270),
|
||||
# Back to page 1 by itself (firmware 0.2.44+): closes an open card and any page but the first
|
||||
# after this many seconds without a touch. Older firmware ignores both keys.
|
||||
'auto_home': (True, None, None),
|
||||
'auto_home_seconds': (120, 30, 3600),
|
||||
}
|
||||
# Firmware before 0.2.44 accepts a `settings` object with exactly its own eleven keys and refuses any
|
||||
# other size, so everything added after it travels as its own key in the layout message. Old firmware
|
||||
# ignores a key it does not know; a new screen with an old add-on keeps what it saved itself.
|
||||
# docs/SETTINGS.md walks through adding one.
|
||||
SETTINGS_BESIDE_BLOCK = ('swipe_pages', 'rotation', 'auto_home', 'auto_home_seconds')
|
||||
|
||||
def validate_settings(data):
|
||||
if not isinstance(data, dict) or set(data) - SETTING_RULES.keys():
|
||||
@@ -250,6 +261,8 @@ def validate_header(data):
|
||||
|
||||
def min_firmware(layout):
|
||||
"""Oldest firmware that still accepts this layout; None when any version works."""
|
||||
if any(t['entity'] == 'screen.settings' for t in layout['tiles']):
|
||||
return (0, 2, 44)
|
||||
if any(t.get('options', {}).get('background') == 'none' for t in layout['tiles']):
|
||||
return (0, 2, 16)
|
||||
if any(t['entity'].split('.')[0] in NEW_DOMAINS for t in layout['tiles']):
|
||||
|
||||
@@ -15,7 +15,7 @@ import tile_icons
|
||||
from updates import Updater
|
||||
|
||||
from aiohttp import ClientSession, ClientTimeout, WSMsgType, web
|
||||
from core import BROADCAST_EVENTS, BUILTIN, TILE_EVENTS, TILE_RESULT_EVENT, apply_tile_event, layout_snapshot, match_screen, HEADER_MIN_FIRMWARE, NAME_TILE_SETTINGS, TILE_BACKGROUNDS, TRANSPORT_MIN_FIRMWARE, alert_data, alert_reference, alert_service, alert_targets, controls_catalogue, device_prefixes, discover, discover_screens, encode, extras, header_items, inbox_prefix, message_action, min_firmware, pack_slots, packets, revision, screen_items, state_message, validate_header, validate_layout, validate_settings
|
||||
from core import BROADCAST_EVENTS, BUILTIN, SETTINGS_BESIDE_BLOCK, TILE_EVENTS, TILE_RESULT_EVENT, apply_tile_event, layout_snapshot, match_screen, HEADER_MIN_FIRMWARE, NAME_TILE_SETTINGS, TILE_BACKGROUNDS, TRANSPORT_MIN_FIRMWARE, alert_data, alert_reference, alert_service, alert_targets, controls_catalogue, device_prefixes, discover, discover_screens, encode, extras, header_items, inbox_prefix, message_action, min_firmware, pack_slots, packets, revision, screen_items, state_message, validate_header, validate_layout, validate_settings
|
||||
import header_bar
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
@@ -599,8 +599,12 @@ class Manager:
|
||||
if 'pages' in layout:
|
||||
message['pages'] = layout['pages']
|
||||
if 'settings' in layout:
|
||||
message['settings'] = {k:v for k,v in layout['settings'].items() if k not in ('swipe_pages','rotation')}
|
||||
# `settings` is the fixed eleven-key block older firmware insists on; everything added
|
||||
# later travels as its own key, which firmware that predates it simply ignores.
|
||||
message['settings'] = {k:v for k,v in layout['settings'].items() if k not in SETTINGS_BESIDE_BLOCK}
|
||||
message['swipe_pages'] = layout['settings'].get('swipe_pages',False)
|
||||
message['auto_home'] = layout['settings'].get('auto_home',True)
|
||||
message['auto_home_seconds'] = layout['settings'].get('auto_home_seconds',120)
|
||||
if screen.get('board')=='guition':
|
||||
message['rotation'] = layout['settings'].get('rotation',0)
|
||||
# The revision the screen echoes on every ping (firmware 0.2.33+; older firmware ignores it).
|
||||
|
||||
@@ -92,6 +92,8 @@ const settingDefinitions = [
|
||||
// Whether the clock shows is up to the top bar now; the add-on keeps show_clock in step for older firmware.
|
||||
["clock_24h", "24-hour clock (off = 12 hour)", "check", true],
|
||||
["home_on_standby", "Return to page 1 after standby", "check", false],
|
||||
["auto_home", "Close cards and return to page 1 by itself (firmware 0.2.44+)", "check", true],
|
||||
["auto_home_seconds", "Return to page 1 after", "minutes", 120, 1, 60],
|
||||
["rotation", "Rotate screen (clockwise)", "rotation", 0],
|
||||
["swipe_pages", "Swipe between pages (Guition: from the edge, firmware 0.2.24+; CYD: quick swipe, 0.2.7+)", "check", false],
|
||||
];
|
||||
@@ -647,6 +649,7 @@ function iconNamed(name) {
|
||||
// What the firmware draws without a choice: Home Assistant's own icon, else the domain icon.
|
||||
function automaticIcon(id) {
|
||||
const icons = inventory.icons, entity = inventory.entities.find((e) => e.id === id), domain = id.split(".")[0];
|
||||
if (icons.builtin?.[id]) return icons.builtin[id];
|
||||
if (entity?.icon) return entity.icon;
|
||||
if (domain === "weather") return icons.weather[entity?.state] || icons.weather.partlycloudy;
|
||||
if (domain === "sun") return icons.sun[entity?.state] || icons.sun.below_horizon;
|
||||
|
||||
Binary file not shown.
@@ -220,6 +220,10 @@ FIXED = (
|
||||
('dots-horizontal', 'F01D8'),
|
||||
# Swing row on the Guition climate card, Home Assistant's icon for swing modes (firmware 0.2.40+).
|
||||
('arrow-oscillating', 'F1C91'),
|
||||
# The settings page on the screen itself (firmware 0.2.44+): its menu and the restart row.
|
||||
('monitor', 'F0379'),
|
||||
('information-outline', 'F02FD'),
|
||||
('restart', 'F0709'),
|
||||
)
|
||||
|
||||
# Pickable icons by name, and every glyph the firmware fonts contain.
|
||||
@@ -230,6 +234,8 @@ GLYPHS = {**{name: codepoint for name, (codepoint, _) in ICONS.items()}, **dict(
|
||||
DEFAULTS = {'light': 'lightbulb', 'climate': 'air-conditioner', 'vacuum': 'robot-vacuum', 'fan': 'fan',
|
||||
'cover': 'window-shutter', 'scene': 'sofa', 'script': 'sofa', 'sensor': 'gauge', 'binary_sensor': 'gauge',
|
||||
'timer': 'timer-outline', 'person': 'account', 'screen': 'clock-outline'}
|
||||
# The cards the screen brings itself: one icon per entity, not per domain.
|
||||
BUILTIN_TILES = {'screen.clock': 'clock-outline', 'screen.settings': 'cog'}
|
||||
FALLBACK = 'power'
|
||||
CONTROL_GLYPHS = ('play', 'pause', 'stop', 'skip-next', 'skip-previous', 'volume-high', 'volume-off', 'arrow-up', 'arrow-down',
|
||||
'arrow-expand-horizontal', 'arrow-collapse-horizontal', 'home-map-marker', 'plus', 'minus', 'chevron-left',
|
||||
@@ -250,6 +256,7 @@ def editor():
|
||||
return {'groups': [{'label': group, 'icons': [{'name': name, 'cp': codepoint, 'label': label} for name, codepoint, label in icons]}
|
||||
for group, icons in GROUPS],
|
||||
'defaults': {domain: GLYPHS[name] for domain, name in DEFAULTS.items()}, 'fallback': GLYPHS[FALLBACK],
|
||||
'builtin': {entity: GLYPHS[name] for entity, name in BUILTIN_TILES.items()},
|
||||
'weather': {state: GLYPHS[name] for state, name in WEATHER.items()},
|
||||
'sun': {'above_horizon': GLYPHS['weather-sunset-down'], 'below_horizon': GLYPHS['weather-sunset-up']},
|
||||
# Glyphs of the direct controls on wide cards, so the mockup previews them.
|
||||
|
||||
Reference in New Issue
Block a user