Give a lamp's modes a page: the effects, palettes, presets, speed and intensity of a WLED behind its colour card, with a drum picker and the effect's name on the tile (0.2.83)
A light that offers effects gets a sparkles key at the top right of its colour card. It opens a page with a row per thing the lamp offers, all from Home Assistant: the light's effect and the select entities of its device (a WLED's colour palette, preset and playlist) with what they are set to, and a slider per number entity of the device (speed, intensity). Names are Home Assistant's own English entity translations, icons its frontend's; nothing about a brand is hardcoded beyond a preferred order of translation keys. A row opens LVGL's roller with every name Home Assistant lists at that moment, asked for when the picker opens (esphome.screen_options, answered with op: options, one page of names per message), the effects alphabetically with Solid on top. The check key at the top right sends the choice as one action; the back key sends nothing. A slider sends number.set_value when let go. While an effect runs, the tile names it instead of the brightness. Firmware 0.2.70: effects_page.h (built when it opens, gone when it closes), Extra.effect/option_rows/number_rows, options_request, a hidden roller widget so ESPHome compiles LVGL's roller, five firmware-only glyphs. App 0.2.83: light_effects.py, the effect attribute, the device rows in the state message, card_options_loop. Checked on the host with a virtual finger (35/35 on both boards) and compiled for both ESP32 boards; CYD 1,675,424 B (91.3 %). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,12 @@
|
||||
## 0.2.83 (firmware 0.2.70)
|
||||
|
||||
A lamp's modes on the screen: effects, palettes and presets of a WLED, and the effect of any light that has one.
|
||||
|
||||
- **An effects page behind the light's colour card.** A light that offers effects (a WLED, a Hue with its candle effect) gets a sparkles key at the top right of its colour card. It opens a page with one row per thing the lamp offers: the light's *Effect*, and the select entities on the lamp's device (a WLED's *Color palette*, *Preset* and *Playlist*), each with what it is set to. Under them a slider per number entity of the device (a WLED's *Speed* and *Intensity*). Nothing is hardcoded for a brand: the rows, their English names and their icons are what Home Assistant lists for the device, so another lamp shows its own rows and a lamp without any shows none.
|
||||
- **A picker that turns like a drum.** A row opens LVGL's roller with every name Home Assistant has at that moment, the effects alphabetically with *Solid* on top and a select's options in Home Assistant's order. Turn it to a name and press the check at the top right: that sends one action (`light.turn_on` with the effect, `select.select_option`; a slider sends `number.set_value` when you let go), the row shows the choice at once, and the back key sends nothing. The list is asked for when the picker opens (event `esphome.screen_options`, answered with `op: options`, one page of names per message), so the screen holds no list while it is closed and a WLED update shows its new effects the next time.
|
||||
- **The tile names the effect.** A light tile reads *TV Simulator* instead of *100 %* while an effect runs; *Solid*, *off* and *None* count as none.
|
||||
- Needs firmware 0.2.70: press **Update** on the screen. Includes everything from 0.2.82. CYD firmware: 1,675,424 bytes, 91.3 % of the update slot (20,288 bytes more than 0.2.82: LVGL's roller is compiled in for the first time, plus the page and five icons).
|
||||
|
||||
## 0.2.82 (firmware 0.2.69)
|
||||
|
||||
The page buttons can make way, and the tiles fill the screen.
|
||||
|
||||
@@ -37,7 +37,7 @@ 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.69'
|
||||
FIRMWARE_VERSION = '0.2.70'
|
||||
# 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.
|
||||
@@ -50,7 +50,7 @@ SETTING_ENTITIES_MIN_FIRMWARE = '0.2.49'
|
||||
DARK_MODE_MIN_FIRMWARE = '0.2.54'
|
||||
# Page buttons: the Previous and Next bar under the tiles, a setting and entity of its own; off, the tiles take its room.
|
||||
PAGE_BUTTONS_MIN_FIRMWARE = '0.2.69'
|
||||
ATTRS = frozenset('brightness percentage current_position current_tilt_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())
|
||||
ATTRS = frozenset('brightness percentage current_position current_tilt_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 effect'.split())
|
||||
# Attributes whose boolean value the screen needs; every other bool stays behind.
|
||||
BOOL_ATTRS = frozenset(['is_volume_muted'])
|
||||
|
||||
@@ -1131,10 +1131,16 @@ def media_extras(attrs):
|
||||
return result or None
|
||||
|
||||
|
||||
def extras(tile, states, forecast=None, tz=None, hourly=None, now=None, device=None):
|
||||
"""Small, pre-computed values the firmware cannot derive itself (time zones, forecasts, a vacuum's device)."""
|
||||
def extras(tile, states, forecast=None, tz=None, hourly=None, now=None, device=None, entries=None, words=None, icon_of=None, device_name=None):
|
||||
"""Small, pre-computed values the firmware cannot derive itself (time zones, forecasts, a vacuum's device, the rows of
|
||||
a light's effects page)."""
|
||||
domain = tile['entity'].split('.')[0]
|
||||
attrs = states.get(tile['entity'], {}).get('attributes', {})
|
||||
if domain == 'light':
|
||||
# The effects page (app 0.2.83): the select and number entities of the light's device, named as Home Assistant
|
||||
# names them; the effect itself travels as the `effect` attribute.
|
||||
import light_effects
|
||||
return light_effects.rows(tile['entity'], device, states, entries, words, icon_of, device_name) or None
|
||||
if domain == 'vacuum':
|
||||
return vacuum_extras(tile, states, device)
|
||||
if domain == 'cover':
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
"""A light's effects page (app 0.2.83 / firmware 0.2.70): what the screen shows for a lamp with modes, such as a WLED.
|
||||
|
||||
Home Assistant is the source of every name and list here. The light's own `effect_list` is one row; the select and
|
||||
number entities on the light's device (WLED: color palette, preset, playlist, speed, intensity) are the others, named
|
||||
as Home Assistant names them in English (frontend/get_translations `entity`) with the icons its frontend shows. Nothing
|
||||
about a brand is hardcoded: a lamp with only an effect list gets one row, a lamp without any gets no page.
|
||||
|
||||
The current values travel in the tile's state message (`x.rows`, `x.nums`); the lists themselves are asked for when a
|
||||
picker opens (`esphome.screen_options` -> `op: options`), one page per message, so the screen holds no list while the
|
||||
picker is closed and always shows what Home Assistant has at that moment.
|
||||
"""
|
||||
import json
|
||||
|
||||
# LightEntityFeature.EFFECT
|
||||
EFFECT_FEATURE = 4
|
||||
# Rows the effects page has room for: the effect plus three selects, and two numbers (a Guition shows them all).
|
||||
SELECT_ROWS = 3
|
||||
NUMBER_ROWS = 2
|
||||
# Where these translation keys occur they come first, in this order; everything else follows by name. This is
|
||||
# presentation only: the rows themselves still come from the device.
|
||||
PREFERRED_SELECTS = ('color_palette', 'preset', 'playlist')
|
||||
PREFERRED_NUMBERS = ('speed', 'intensity')
|
||||
# A message to the screen is at most 4096 bytes; the names of one page stay well under it.
|
||||
PAGE_BYTES = 3400
|
||||
NAME_LIMIT = 48
|
||||
OPTIONS_EVENT = 'esphome.screen_options'
|
||||
|
||||
|
||||
def supports_effects(attrs):
|
||||
"""Whether the light offers effects: Home Assistant's EFFECT feature and a list to choose from."""
|
||||
features = (attrs or {}).get('supported_features')
|
||||
return isinstance(features, int) and bool(features & EFFECT_FEATURE) and isinstance((attrs or {}).get('effect_list'), list)
|
||||
|
||||
|
||||
def effect_options(attrs):
|
||||
"""The light's effects as the picker lists them: alphabetically, with "Solid" (WLED's plain colour) first."""
|
||||
names = [str(v) for v in (attrs or {}).get('effect_list') or [] if isinstance(v, (str, int, float)) and not isinstance(v, bool)]
|
||||
names = [n for n in names if n.strip()]
|
||||
solid = [n for n in names if n.strip().lower() == 'solid']
|
||||
rest = sorted((n for n in names if n.strip().lower() != 'solid'), key=lambda n: (n.casefold(), n))
|
||||
return solid[:1] + rest
|
||||
|
||||
|
||||
def _numeric(options):
|
||||
"""A select whose every option is a bare number (WLED's live override: 0, 1, 2) is a technical setting, not a mode."""
|
||||
return all(isinstance(o, str) and o.strip().lstrip('-').isdigit() for o in options)
|
||||
|
||||
|
||||
def _qualifies(item, states):
|
||||
"""A select or number of the device the page can show: enabled, not hidden, not diagnostic."""
|
||||
eid = item.get('entity_id') or ''
|
||||
if item.get('disabled_by') or item.get('hidden_by') or item.get('entity_category') == 'diagnostic':
|
||||
return False
|
||||
state = states.get(eid)
|
||||
if not isinstance(state, dict):
|
||||
return False
|
||||
attrs = state.get('attributes') or {}
|
||||
if eid.startswith('select.'):
|
||||
options = attrs.get('options')
|
||||
return isinstance(options, list) and bool(options) and all(isinstance(o, str) for o in options) and not _numeric(options)
|
||||
if eid.startswith('number.'):
|
||||
try:
|
||||
return float(attrs.get('min')) < float(attrs.get('max'))
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def _order(items, preferred, names):
|
||||
def rank(item):
|
||||
key = item.get('translation_key') or ''
|
||||
return (preferred.index(key) if key in preferred else len(preferred), names[item['entity_id']].casefold())
|
||||
return sorted(items, key=rank)
|
||||
|
||||
|
||||
def entity_name(eid, entry, states, words):
|
||||
"""Home Assistant's English name of a device's entity: its integration's translation for the entity's key
|
||||
(component.wled.entity.select.color_palette.name = "Color palette"), else the name Home Assistant shows, without the
|
||||
device's name in front of it ("VUELTA Kleurenpalet" is "Kleurenpalet")."""
|
||||
entry = entry if isinstance(entry, dict) else {}
|
||||
domain = eid.split('.', 1)[0]
|
||||
if isinstance(words, dict) and entry.get('platform') and entry.get('translation_key'):
|
||||
word = words.get(f"component.{entry['platform']}.entity.{domain}.{entry['translation_key']}.name")
|
||||
if isinstance(word, str) and word and '{' not in word:
|
||||
return word
|
||||
name = entry.get('name') or entry.get('original_name') or (states.get(eid, {}).get('attributes') or {}).get('friendly_name') or eid
|
||||
return str(name)
|
||||
|
||||
|
||||
def strip_device_name(name, device_name):
|
||||
""""VUELTA Kleurenpalet" without the device's name, as Home Assistant's device page shows it."""
|
||||
if isinstance(device_name, str) and device_name and name.lower().startswith(device_name.lower() + ' '):
|
||||
return name[len(device_name) + 1:] or name
|
||||
return name
|
||||
|
||||
|
||||
def siblings(entity, device, states):
|
||||
"""The select and number entities on the light's device that its effects page shows, in page order."""
|
||||
selects, numbers = [], []
|
||||
for item in device or ():
|
||||
eid = item.get('entity_id') or ''
|
||||
if eid == entity or not _qualifies(item, states):
|
||||
continue
|
||||
(selects if eid.startswith('select.') else numbers).append(item)
|
||||
return selects, numbers
|
||||
|
||||
|
||||
def related(entity, device, states):
|
||||
"""Entity ids the light's card reads besides its own, for the manager's watch list."""
|
||||
selects, numbers = siblings(entity, device, states)
|
||||
return tuple(item['entity_id'] for item in selects + numbers)
|
||||
|
||||
|
||||
def rows(entity, device, states, entries=None, words=None, icon_of=None, device_name=None):
|
||||
"""The `rows` (selects) and `nums` (numbers) of the light's effects page for the tile's extra block, or {}.
|
||||
|
||||
`entries` is the registry by entity id (names and translation keys), `words` Home Assistant's English entity
|
||||
translations, `icon_of(eid, state, attributes, entry)` the codepoint of the icon Home Assistant shows, or None."""
|
||||
selects, numbers = siblings(entity, device, states)
|
||||
entries = entries or {}
|
||||
names = {}
|
||||
for item in selects + numbers:
|
||||
eid = item['entity_id']
|
||||
entry = entries.get(eid, item)
|
||||
names[eid] = strip_device_name(entity_name(eid, entry, states, words), device_name)
|
||||
result = {}
|
||||
found = []
|
||||
for item in _order(selects, PREFERRED_SELECTS, names)[:SELECT_ROWS]:
|
||||
eid = item['entity_id']
|
||||
state = states[eid]
|
||||
attrs = state.get('attributes') or {}
|
||||
row = {'e': eid, 'n': _short(names[eid], 32), 's': _short(state.get('state') if isinstance(state.get('state'), str) else '', NAME_LIMIT),
|
||||
'c': len(attrs.get('options') or [])}
|
||||
icon = icon_of(eid, state.get('state'), attrs, entries.get(eid, item)) if icon_of else None
|
||||
if icon:
|
||||
row['i'] = icon
|
||||
found.append(row)
|
||||
if found:
|
||||
result['rows'] = found
|
||||
found = []
|
||||
for item in _order(numbers, PREFERRED_NUMBERS, names)[:NUMBER_ROWS]:
|
||||
eid = item['entity_id']
|
||||
state = states[eid]
|
||||
attrs = state.get('attributes') or {}
|
||||
row = {'e': eid, 'n': _short(names[eid], 32), 'lo': _number(attrs.get('min'), 0), 'hi': _number(attrs.get('max'), 100),
|
||||
'st': _number(attrs.get('step'), 1)}
|
||||
value = _number(state.get('state'), None)
|
||||
if value is not None:
|
||||
row['v'] = value
|
||||
icon = icon_of(eid, state.get('state'), attrs, entries.get(eid, item)) if icon_of else None
|
||||
if icon:
|
||||
row['i'] = icon
|
||||
found.append(row)
|
||||
if found:
|
||||
result['nums'] = found
|
||||
return result
|
||||
|
||||
|
||||
def options_for(entity, states, layout_entities=(), device_of=None):
|
||||
"""The names a picker lists for `entity`, or None when the screen may not ask for them: a light on the layout (its
|
||||
effects) or a select on the device of such a light (its options). `device_of(light)` gives that device's entries."""
|
||||
state = states.get(entity)
|
||||
if not isinstance(state, dict):
|
||||
return None
|
||||
attrs = state.get('attributes') or {}
|
||||
if entity.startswith('light.'):
|
||||
return effect_options(attrs) if entity in layout_entities and supports_effects(attrs) else None
|
||||
if entity.startswith('select.'):
|
||||
for light in layout_entities:
|
||||
if light.startswith('light.') and device_of and entity in related(light, device_of(light), states):
|
||||
options = attrs.get('options')
|
||||
return [str(o) for o in options if isinstance(o, str)] if isinstance(options, list) else None
|
||||
return None
|
||||
|
||||
|
||||
def pages(names):
|
||||
"""The names cut into pages whose message stays under the screen's limit (216 WLED effects are one page)."""
|
||||
result, page, size = [], [], 0
|
||||
for name in names:
|
||||
name = _short(name, NAME_LIMIT)
|
||||
cost = len(json.dumps(name, ensure_ascii=False).encode()) + 1
|
||||
if page and size + cost > PAGE_BYTES:
|
||||
result.append(page)
|
||||
page, size = [], 0
|
||||
page.append(name)
|
||||
size += cost
|
||||
if page or not result:
|
||||
result.append(page)
|
||||
return result
|
||||
|
||||
|
||||
def message(entity, page, all_pages):
|
||||
"""One page of a picker's names, as the screen parses it (`op: options`)."""
|
||||
page = max(0, min(int(page), len(all_pages) - 1))
|
||||
return {'v': 1, 'op': 'options', 'e': entity, 'i': page, 'n': len(all_pages), 'o': all_pages[page]}
|
||||
|
||||
|
||||
def _short(value, limit):
|
||||
value = str(value if value is not None else '')
|
||||
return value if len(value) <= limit else value[:limit - 1] + '…'
|
||||
|
||||
|
||||
def _number(value, fallback):
|
||||
try:
|
||||
number = float(value)
|
||||
except (TypeError, ValueError):
|
||||
return fallback
|
||||
if number != number or number in (float('inf'), float('-inf')):
|
||||
return fallback
|
||||
return int(number) if number.is_integer() else round(number, 3)
|
||||
@@ -14,6 +14,7 @@ import camera_feed
|
||||
import claude_skill
|
||||
from firmware import Firmware
|
||||
import ha_catalogue
|
||||
import light_effects
|
||||
import tile_icons
|
||||
from updates import Updater
|
||||
|
||||
@@ -25,6 +26,11 @@ import header_bar
|
||||
import history_card
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
|
||||
def row_icon(eid, state, attrs, entry):
|
||||
"""Codepoint of the icon Home Assistant shows for a row of a light's effects page, when the screen's fonts carry it."""
|
||||
return tile_icons.ha_icon(attrs) or tile_icons.default_glyph(eid, state, attrs, entry)
|
||||
|
||||
LOG = logging.getLogger('screen_manager')
|
||||
|
||||
REGISTRY_EVENTS = ('entity_registry_updated', 'device_registry_updated', 'area_registry_updated')
|
||||
@@ -124,6 +130,8 @@ class HomeAssistant:
|
||||
self.history_requests = asyncio.Queue()
|
||||
# A camera a screen opens full screen (firmware 0.2.57+): {inbox, entity}, for Manager.camera_loop.
|
||||
self.camera_requests = asyncio.Queue()
|
||||
# The names a light's picker lists (firmware 0.2.70+): {inbox, entity, page}, for Manager.card_options_loop.
|
||||
self.options_requests = asyncio.Queue()
|
||||
|
||||
async def request(self, kind, **data):
|
||||
if self.ws is None or self.ws.closed:
|
||||
@@ -161,6 +169,8 @@ class HomeAssistant:
|
||||
self.history_requests.put_nowait(body)
|
||||
elif event.get('event_type') == 'esphome.screen_camera':
|
||||
self.camera_requests.put_nowait(body)
|
||||
elif event.get('event_type') == light_effects.OPTIONS_EVENT:
|
||||
self.options_requests.put_nowait(body)
|
||||
elif event.get('event_type') == 'state_changed':
|
||||
eid = body.get('entity_id')
|
||||
if body.get('new_state'):
|
||||
@@ -295,6 +305,7 @@ class HomeAssistant:
|
||||
await self.request('subscribe_events', event_type='esphome.screen_setting')
|
||||
await self.request('subscribe_events', event_type='esphome.screen_history')
|
||||
await self.request('subscribe_events', event_type='esphome.screen_camera')
|
||||
await self.request('subscribe_events', event_type=light_effects.OPTIONS_EVENT)
|
||||
for event_type in (*REGISTRY_EVENTS, *BROADCAST_EVENTS, *TILE_EVENTS, *SERVICE_EVENTS):
|
||||
await self.request('subscribe_events', event_type=event_type)
|
||||
self.states = {s['entity_id']: s for s in await self.request('get_states')}
|
||||
@@ -818,6 +829,37 @@ class Manager:
|
||||
self.retries[inbox] = failures + 1
|
||||
self.retry_at[inbox] = due
|
||||
|
||||
# ----- The names a light's picker lists (app 0.2.83, firmware 0.2.70+) -----
|
||||
async def card_options_loop(self):
|
||||
"""Answer a screen that opens a picker on a light's effects page: the effects of the light, or the options of a
|
||||
select on its device, one page per message."""
|
||||
while True:
|
||||
request = await self.ha.options_requests.get()
|
||||
try:
|
||||
await self.answer_options(request)
|
||||
except (ClientError, ConnectionError, TimeoutError, OSError, ValueError) as error:
|
||||
LOG.info('No options for %s (%s)', request.get('entity') if isinstance(request, dict) else '?', type(error).__name__)
|
||||
|
||||
async def answer_options(self, request):
|
||||
"""One screen's request: a light on its own layout, or a select on that light's device; a page it asked for."""
|
||||
if not isinstance(request, dict):
|
||||
return
|
||||
inbox = self.aliases.get(request.get('inbox'), request.get('inbox'))
|
||||
entity = request.get('entity')
|
||||
try:
|
||||
page = int(request.get('page') or 0)
|
||||
except (TypeError, ValueError):
|
||||
page = 0
|
||||
layout, screen = self.layouts.get(inbox), self.screen(inbox) if isinstance(inbox, str) else None
|
||||
if not isinstance(entity, str) or not layout or not screen or not screen.get('online'):
|
||||
return
|
||||
names = light_effects.options_for(entity, self.ha.states, [tile['entity'] for tile in layout['tiles']], self.device_entries)
|
||||
action = self.transport(inbox, screen)
|
||||
if names is None or not action:
|
||||
return
|
||||
all_pages = light_effects.pages(names)
|
||||
await self.ha.send(inbox, light_effects.message(entity, page, all_pages), action)
|
||||
|
||||
# ----- History on a detail card (firmware 0.2.51+) -----
|
||||
async def card_history_loop(self):
|
||||
"""Answer the history a screen asks for when a card opens, a few at a time."""
|
||||
@@ -918,8 +960,18 @@ class Manager:
|
||||
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())
|
||||
if tile['entity'].startswith('light.'):
|
||||
return light_effects.related(tile['entity'], self.device_entries(tile['entity']), self.ha.states)
|
||||
return ()
|
||||
|
||||
def device_name_of(self, entity):
|
||||
"""The name of the entity's device as Home Assistant shows it (the user's name first), or None."""
|
||||
device = self.registry_index().get(entity, {}).get('device_id')
|
||||
for item in getattr(self.ha, 'devices', None) or ():
|
||||
if isinstance(item, dict) and item.get('id') == device:
|
||||
return item.get('name_by_user') or item.get('name')
|
||||
return None
|
||||
|
||||
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),
|
||||
getattr(self.ha, 'state_words', None))
|
||||
@@ -1088,9 +1140,13 @@ class Manager:
|
||||
# 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), a cover's card its battery (0.2.58).
|
||||
device=self.device_entries(tile['entity']) if tile['entity'].startswith(('vacuum.', 'cover.')) else None
|
||||
device=self.device_entries(tile['entity']) if tile['entity'].startswith(('vacuum.', 'cover.', 'light.')) else None
|
||||
entry=self.registry_index().get(tile['entity'])
|
||||
extra=extras(tile,self.ha.states,forecast,getattr(self.ha,'time_zone',None),hourly,device=device)
|
||||
# A light's effects page (app 0.2.83) names the device's selects and numbers as Home Assistant does, with its icons.
|
||||
light=tile['entity'].startswith('light.')
|
||||
extra=extras(tile,self.ha.states,forecast,getattr(self.ha,'time_zone',None),hourly,device=device,
|
||||
entries=self.registry_index() if light else None,words=getattr(self.ha,'state_words',None) if light else None,
|
||||
icon_of=row_icon if light else None,device_name=self.device_name_of(tile['entity']) if light else None)
|
||||
if tile['entity'].startswith('vacuum.'):
|
||||
ha_catalogue.chip_words(extra,tile['entity'],self.ha.states,device,getattr(self.ha,'state_words',None))
|
||||
message=state_message(index,tile,self.ha.states,extra,
|
||||
@@ -1843,7 +1899,7 @@ async def main():
|
||||
LOG.error('Camera images are off: port %d is not available (%s)', camera_feed.port(), error)
|
||||
try:
|
||||
await asyncio.gather(ha.run(), manager.run(), manager.history_loop(), manager.updates.run(),
|
||||
manager.alert_loop(), manager.tile_loop(), manager.card_history_loop(), manager.camera_loop())
|
||||
manager.alert_loop(), manager.tile_loop(), manager.card_history_loop(), manager.camera_loop(), manager.card_options_loop())
|
||||
finally:
|
||||
await cameras.cleanup()
|
||||
await runner.cleanup()
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
-1
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -5,8 +5,8 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>ESP Screens</title>
|
||||
<script type="module" crossorigin src="./assets/index-Cp13kZoB.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CKR1N3aP.css">
|
||||
<script type="module" crossorigin src="./assets/index-CteHDGjl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-C6Sfd-UM.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -338,6 +338,13 @@ HA_DEFAULTS = (
|
||||
('speaker-pause', 'F1B73'),
|
||||
('speaker-play', 'F1B72'),
|
||||
('speedometer', 'F04C5'),
|
||||
# A light's effects page (firmware 0.2.70+): Home Assistant's icons for an effect and WLED's palette, preset,
|
||||
# playlist and intensity; the speedometer above serves its speed.
|
||||
('creation', 'F0674'),
|
||||
('palette-outline', 'F0E0C'),
|
||||
('playlist-play', 'F0411'),
|
||||
('play-speed', 'F08FF'),
|
||||
('brightness-6', 'F00DF'),
|
||||
('spoon-sugar', 'F1429'),
|
||||
('sprout-outline', 'F0E67'),
|
||||
('square', 'F0764'),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: ESP Screen Manager
|
||||
version: "0.2.82"
|
||||
version: "0.2.83"
|
||||
slug: esp_screen_manager
|
||||
description: Choose tiles for your CYD and Guition touchscreen, without reflashing.
|
||||
url: https://github.com/MaxGramser/homeassistant_espscreen
|
||||
|
||||
Reference in New Issue
Block a user