Show live values on the mockup, blink a screen to identify it, try an alert, copy and share layouts (0.2.74)

The tiles draw what Home Assistant reports (a new /api/states answers for the tiles on the page); the ···
menu gets Identify (the screen's own show_alert with flash), Copy layout from another screen, Export and
Import as JSON; the Alerts page opens with Try it (/api/alerts/test, one screen or all); the library filters
by room and hides what is placed, with lit icons for what is on; ⌘K searches screens, entities and actions;
the Update badge lists what's new from the changelog (copied into the image) and a running update shows a
progress bar with the ESPHome stage and the last log line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-18 08:21:37 +02:00
parent ef8e94f6d1
commit 6148dfd211
27 changed files with 818 additions and 79 deletions
+45
View File
@@ -1623,6 +1623,48 @@ def create_app(manager, development=False):
manager.registry_index().get(t['entity']),getattr(manager.ha,'state_words',None)),
'attributes':state_message(i,t,manager.ha.states)['a'],
'options':t.get('options',{})} for i,t in enumerate(layout['tiles'])]})
async def states(request):
"""Live values for the editor's mockup (app 0.2.74): the state, Home Assistant's word and the attributes a
card shows, for the tiles on the page, saved or not. At most sixty entities per request."""
index = manager.registry_index()
result = {}
for eid in request.query.getall('entity', [])[:60]:
if not isinstance(eid, str) or eid not in manager.ha.states:
continue
state = manager.ha.states.get(eid, {})
entry = index.get(eid)
message = state_message(0, {'entity': eid, 'name': ''}, manager.ha.states,
precision=header_bar.precision_of(entry) if eid.startswith('sensor.') else None)
result[eid] = {'state': message['state'], 'a': message['a'],
'word': state_word(eid, state.get('state'), state.get('attributes'), entry, getattr(manager.ha, 'state_words', None))}
return web.json_response({'states': result})
def one_alert_target(inbox):
screen = manager.screen(inbox)
if screen is None:
raise ValueError('Unknown screen.')
ready, skipped = alert_targets([screen])
if not ready:
raise ValueError(f"{screen['name']} can't show an alert: {skipped[0][1] if skipped else 'not ready'}.")
return ready[0]
async def identify(request):
"""Identify (app 0.2.74): the screen shows a short card and blinks its backlight, so you know which one it is."""
screen = one_alert_target(request.match_info['inbox'])
data, _ = alert_data({'title': f"This is {screen['name']}", 'subtitle': 'Identify, from ESP Screens', 'icon': 'bell-ring',
'color': 'blue', 'button_text': 'OK', 'timeout': 8, 'flash': True})
await manager.ha.call(alert_service(screen['node']), data)
return web.json_response({'ok': True})
async def test_alert(request):
"""Alerts → Try it (app 0.2.74): one alert to one screen or to every screen, with the fields an automation sends."""
body = await request.json()
if not isinstance(body, dict):
raise ValueError('Invalid alert.')
data = body.get('data') if isinstance(body.get('data'), dict) else {}
if body.get('screen') == 'all':
return web.json_response(await manager.broadcast(BROADCAST_SHOW, data))
screen = one_alert_target(str(body.get('screen', '')))
service, unusable = alert_data(data)
await manager.ha.call(alert_service(screen['node']), service)
return web.json_response({'sent': 1, 'failed': 0, 'skipped': 0, 'unusable': unusable})
async def install_claude_skill(request):
"""Settings → Claude → Install: writes the skill into Home Assistant's configuration folder, only on request."""
return web.json_response(claude_skill.install(manager.skill_dir))
@@ -1667,6 +1709,9 @@ def create_app(manager, development=False):
app.router.add_get('/', index)
app.router.add_get('/api/inventory', inventory)
app.router.add_get('/api/capabilities', capabilities)
app.router.add_get('/api/states', states)
app.router.add_post('/api/screens/{inbox}/identify', identify)
app.router.add_post('/api/alerts/test', test_alert)
app.router.add_get('/api/entity-actions', entity_actions)
app.router.add_post('/api/header-preview', header_preview)
app.router.add_post('/api/claude-skill', install_claude_skill)