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>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""HA Ingress app. HA writes: text.set_value on discovered inboxes (or the screen_message action on firmware 0.2.33+), each screen's alert actions when an alert event for every screen fires, and one persistent notification when a nightly update stops."""
|
||||
import asyncio
|
||||
import contextlib
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
@@ -671,11 +672,14 @@ class Manager:
|
||||
return self._by_device.get(device, []) if device else []
|
||||
|
||||
def related_entities(self, tile):
|
||||
"""Entities a card reads besides its own: a vacuum's cleaning mode and water selects and its battery sensor."""
|
||||
if not tile['entity'].startswith('vacuum.'):
|
||||
return ()
|
||||
from core import vacuum_related
|
||||
return tuple(vacuum_related(tile['entity'], self.device_entries(tile['entity']), self.ha.states).values())
|
||||
"""Entities a card reads besides its own: a vacuum's cleaning mode and water selects and its battery sensor, a
|
||||
cover's battery sensor."""
|
||||
from core import cover_related, vacuum_related
|
||||
if tile['entity'].startswith('vacuum.'):
|
||||
return tuple(vacuum_related(tile['entity'], self.device_entries(tile['entity']), self.ha.states).values())
|
||||
if tile['entity'].startswith('cover.'):
|
||||
return tuple(cover_related(tile['entity'], self.device_entries(tile['entity']), self.ha.states).values())
|
||||
return ()
|
||||
|
||||
def header_message(self, layout):
|
||||
return header_bar.message(layout, self.ha.states, self.registry_index(), getattr(self.ha, 'units', {}), getattr(self.ha, 'time_zone', None))
|
||||
@@ -802,8 +806,8 @@ class Manager:
|
||||
forecast=await self.cached(self.forecasts, entity, FORECAST_SECONDS, (lambda: self.ha.forecast(entity)) if 'daily' in kinds else nothing)
|
||||
# Hourly forecasts feed the weather card's next-hours strip (0.2.23+); refreshed every half hour.
|
||||
hourly=await self.cached(self.forecasts, (entity,'hourly'), FORECAST_SECONDS, (lambda: self.ha.forecast(entity,'hourly')) if 'hourly' in kinds else nothing)
|
||||
# A vacuum's card also reads selects and the battery sensor of its device (app 0.2.46).
|
||||
device=self.device_entries(tile['entity']) if tile['entity'].startswith('vacuum.') else None
|
||||
# A vacuum's card also reads selects and the battery sensor of its device (app 0.2.46), a cover's card its battery (0.2.58).
|
||||
device=self.device_entries(tile['entity']) if tile['entity'].startswith(('vacuum.', 'cover.')) else None
|
||||
message=state_message(index,tile,self.ha.states,extras(tile,self.ha.states,forecast,getattr(self.ha,'time_zone',None),hourly,device=device))
|
||||
if tile['entity'].startswith('sensor.') and hasattr(self.ha,'history'):
|
||||
hours=tile.get('options',{}).get('history_hours',24)
|
||||
@@ -1158,8 +1162,12 @@ def create_app(manager, development=False):
|
||||
app = web.Application(middlewares=[guard], client_max_size=16*1024)
|
||||
static = Path(__file__).parent / 'static'
|
||||
|
||||
# The page asks for its script and styles with a stamp of their content, so a browser that keeps old copies
|
||||
# anyway (Safari did after an update) never runs an old script against a new page.
|
||||
stamp = hashlib.sha1(b''.join((static / name).read_bytes() for name in ('app.js', 'style.css'))).hexdigest()[:12]
|
||||
page = (static / 'index.html').read_text().replace('"static/app.js"', f'"static/app.js?v={stamp}"').replace('"static/style.css"', f'"static/style.css?v={stamp}"')
|
||||
async def index(request):
|
||||
return web.FileResponse(static / 'index.html')
|
||||
return web.Response(text=page, content_type='text/html')
|
||||
def light_payload(screens=None):
|
||||
"""Screens and update status: everything that changes while the page is open."""
|
||||
if screens is None:
|
||||
|
||||
Reference in New Issue
Block a user