Files
homeassistant_espscreen/packages/guition.yaml
T

5777 lines
266 KiB
YAML

# GENERATED by tools/generate_packages.py; edit the source board profile.
# Guition ESP32-S3-4848S040 / 4-inch EU wallbox, 480x480 IPS + GT911.
# Use guition-device.yaml; see docs/GUITION.md. CYD hardware is a separate profile.
substitutions:
DYNAMIC_TILES: "true"
DASHBOARD_INBOX_INTERNAL: "false"
DEVICE_NAME: "guition-new"
DEVICE_FRIENDLY_NAME: "Mijn Guition"
CALIBRATION_ON_BOOT: "false"
TILE_COUNT: "1" # 1-10; <=6 hides page navigation
DIRECT_ACTIONS: "true" # true = call Home Assistant services directly, false = only emit action events
ROOM_NAME: "Kies tegels in HA" # room name shown in the top-left header
TIME_24H: "true" # true = 24h format, false = 12h format
## DIM settings
AUTO_DIM: "true" # true = enable auto dimming
AUTO_DIM_TIMEOUT: "600" # seconds after last touch
MAX_BRIGHTNESS: "100" # backlight brightness in percent
DIM_BRIGHTNESS: "20" # dimmed backlight brightness in percent
NIGHT_MODE: "true" # true = use separate dim level at night
NIGHT_START_HOUR: "22" # 0-23
NIGHT_END_HOUR: "7" # 0-23
NIGHT_DIM_BRIGHTNESS: "10" # backlight brightness in percent at night
# ============================================================
# ACCENTKLEUREN — automatisch bepaald uit het Home Assistant-domein
#
# Het domein is het stuk voor de punt in het entity_id:
# script.goede_morgen -> "script" -> ACCENT_ACTION
# light.woonkamer_2 -> "light" -> ACCENT_LIGHT
# vacuum.s8 -> "vacuum" -> ACCENT_DEVICE
#
# Dat is Home Assistant's eigen indeling, dus een tegel krijgt vanzelf de
# juiste kleur zodra je er een entity in zet — je hoeft per tegel geen
# kleuren meer te kiezen. Het onderscheid dat telt: een script/scene vuur
# je af, een apparaat zet je aan of uit. Die twee horen niet hetzelfde
# te zijn.
#
# Zet TILE*_CIRCLE_ACTIVE_COLOR / TILE*_ICON_DISABLED_COLOR op
# ${ACCENT_AUTO} om dit te gebruiken, of op een echte hexwaarde om die
# ene tegel handmatig te kleuren (zie tegel 8 — rood zodra er post ligt).
# ============================================================
ACCENT_AUTO: "0x1000000" # sentinel: bewust buiten het 24-bits kleurbereik
ACCENT_ACTION: "0x8B5CF6" # script / scene / button / input_button / automation
ACCENT_LIGHT: "0xFEC600" # light
ACCENT_DEVICE: "0x00C5EC" # switch, fan, cover, climate, vacuum, media_player, rest
# ============================================================
# FONT GLYPHS
# List every unique MDI icon used across your tiles — each value must appear exactly once.
# Reusing the same icon on multiple tiles is fine: just list that glyph once here.
# If you use fewer than 6 unique icons, leave the extra slots with their default values
# (unused glyphs add a few bytes to the font but cause no errors).
# ============================================================
MDI_GLYPH_1: "\U000F001B"
MDI_GLYPH_2: "\U000F0387" # mdi:music
MDI_GLYPH_3: "\U000F02E3"
MDI_GLYPH_4: "\U000F059C"
MDI_GLYPH_5: "\U000F0425"
MDI_GLYPH_6: "\U000F070D"
MDI_GLYPH_7: "\U000F06B5" # mdi:lamp
MDI_GLYPH_8: "\U000F06EE" # mdi:mailbox
MDI_GLYPH_9: "\U000F04B9" # mdi:sofa
MDI_GLYPH_10: "\U000F002A" # mdi:alarm-light
# ============================================================
# TILE 1
# ============================================================
# --- basic settings ---
TILE1_ENTITY: "light.voorbeeld_lamp" # main Home Assistant entity for this tile
TILE1_STATE_ENTITY: "light.voorbeeld_lamp" # entity used for active/inactive state styling
TILE1_TITLE: "Lamp" # tile title shown in the UI
TILE1_ICON: "\U000F001B" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE1_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE1_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE1_LONGPRESS: "slider" # long press mode: none | slider | action
TILE1_VALUE_MODE: "auto" # value mode: auto | brightness | percentage | preset | text
TILE1_LABEL_OFF: "Uit" # text shown when entity is off
TILE1_LABEL_ON: "Aan" # text shown for text-mode tiles when entity is on
TILE1_ACTIVE_STATE: "" # optional: exact HA state that counts as "active" (empty = default "on")
# --- colors ---
TILE1_CIRCLE_ACTIVE_COLOR: "${ACCENT_AUTO}" # icon circle color when active
TILE1_CIRCLE_DISABLED_COLOR: "0x1B2D3C" # icon circle color when inactive
TILE1_ICON_ACTIVE_COLOR: "0xFFFFFF" # icon color when active
TILE1_ICON_DISABLED_COLOR: "${ACCENT_AUTO}" # icon color when inactive
TILE1_BG_ACTIVE_COLOR: "0xF5F3EB" # tile background color when active
TILE1_BG_DISABLED_COLOR: "0x293F50" # tile background color when inactive
TILE1_TITLE_ACTIVE_COLOR: "0x142334" # title color when active
TILE1_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
TILE1_VALUE_ACTIVE_COLOR: "0x526477" # value color when active
TILE1_VALUE_DISABLED_COLOR: "0xB9CDDB" # value color when inactive
# --- advanced settings ---
TILE1_TAP_SERVICE: "" # optional service override, e.g. light.toggle
TILE1_TAP_PARAM_KEY: "" # optional extra service parameter key
TILE1_TAP_PARAM_VAL: "" # optional extra service parameter value
# if LONGPRESS = slider:
TILE1_LONGPRESS_SLIDER: "auto" # slider type: auto | brightness | percentage | cover_position
TILE1_LONGPRESS_OFF_VALUE: "0" # initial slider value when entity is off
# if LONGPRESS = action:
TILE1_LONGPRESS_ACTION: "" # entity_id to fire on long press
TILE1_LONGPRESS_ACTION_TYPE: "" # entity type: light | fan | switch | scene | script | cover | climate (empty = same as TILE*_TYPE)
TILE1_LONGPRESS_ACTION_SERVICE: "" # service to call (default: activate/turn_on — set e.g. "light.toggle" to toggle instead)
# if TYPE = climate:
TILE1_CLIMATE_MIN_TEMP: "16.0" # minimum target temperature shown in the thermostat overlay
TILE1_CLIMATE_MAX_TEMP: "32.0" # maximum target temperature shown in the thermostat overlay
TILE1_CLIMATE_STEP: "1.0" # target temperature step size
TILE1_CLIMATE_CURRENT_TEMP_ATTRIBUTE: "current_temperature" # HA attribute for current room temperature
TILE1_CLIMATE_TARGET_TEMP_ATTRIBUTE: "temperature" # HA attribute for target temperature
TILE1_CLIMATE_HUMIDITY_ATTRIBUTE: "current_humidity" # HA attribute for humidity; leave as-is if unavailable
# ============================================================
# TILE 2
# ============================================================
# --- basic settings ---
TILE2_ENTITY: "switch.voorbeeld_stekker" # main Home Assistant entity for this tile
TILE2_STATE_ENTITY: "switch.voorbeeld_stekker" # entity used for active/inactive state styling
TILE2_TITLE: "Stekker" # tile title shown in the UI
TILE2_ICON: "\U000F0387" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE2_TYPE: "switch" # tile type: light | fan | switch | scene | script | cover | climate
TILE2_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE2_LONGPRESS: "none" # long press mode: none | slider | action
TILE2_VALUE_MODE: "auto" # value mode: auto | brightness | percentage | preset | text
TILE2_LABEL_OFF: "Uit" # text shown when entity is off
TILE2_LABEL_ON: "Aan" # text shown for text-mode tiles when entity is on
TILE2_ACTIVE_STATE: "" # optional: exact HA state that counts as "active" (empty = default "on")
# --- colors ---
TILE2_CIRCLE_ACTIVE_COLOR: "${ACCENT_AUTO}" # icon circle color when active
TILE2_CIRCLE_DISABLED_COLOR: "0x1B2D3C" # icon circle color when inactive
TILE2_ICON_ACTIVE_COLOR: "0xFFFFFF" # icon color when active
TILE2_ICON_DISABLED_COLOR: "${ACCENT_AUTO}" # icon color when inactive
TILE2_BG_ACTIVE_COLOR: "0xF5F3EB" # tile background color when active
TILE2_BG_DISABLED_COLOR: "0x293F50" # tile background color when inactive
TILE2_TITLE_ACTIVE_COLOR: "0x142334" # title color when active
TILE2_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
TILE2_VALUE_ACTIVE_COLOR: "0x526477" # value color when active
TILE2_VALUE_DISABLED_COLOR: "0xB9CDDB" # value color when inactive
# --- advanced settings ---
TILE2_TAP_SERVICE: "" # optional service override, e.g. light.toggle
TILE2_TAP_PARAM_KEY: "" # optional extra service parameter key
TILE2_TAP_PARAM_VAL: "" # optional extra service parameter value
# if LONGPRESS = slider:
TILE2_LONGPRESS_SLIDER: "auto" # slider type: auto | brightness | percentage | cover_position
TILE2_LONGPRESS_OFF_VALUE: "0" # initial slider value when entity is off
# if LONGPRESS = action:
TILE2_LONGPRESS_ACTION: "" # entity_id to fire on long press
TILE2_LONGPRESS_ACTION_TYPE: "" # entity type: light | fan | switch | scene | script | cover | climate (empty = same as TILE*_TYPE)
TILE2_LONGPRESS_ACTION_SERVICE: "" # service to call (default: activate/turn_on — set e.g. "light.toggle" to toggle instead)
# if TYPE = climate:
TILE2_CLIMATE_MIN_TEMP: "5.0" # minimum target temperature shown in the thermostat overlay
TILE2_CLIMATE_MAX_TEMP: "30.0" # maximum target temperature shown in the thermostat overlay
TILE2_CLIMATE_STEP: "0.5" # target temperature step size
TILE2_CLIMATE_CURRENT_TEMP_ATTRIBUTE: "current_temperature" # HA attribute for current room temperature
TILE2_CLIMATE_TARGET_TEMP_ATTRIBUTE: "temperature" # HA attribute for target temperature
TILE2_CLIMATE_HUMIDITY_ATTRIBUTE: "current_humidity" # HA attribute for humidity; leave as-is if unavailable
# ============================================================
# TILE 3
# ============================================================
# --- basic settings ---
TILE3_ENTITY: "scene.voorbeeld_avond" # main Home Assistant entity for this tile
TILE3_STATE_ENTITY: "scene.voorbeeld_avond" # entity used for active/inactive state styling
TILE3_TITLE: "Avond" # tile title shown in the UI
TILE3_ICON: "\U000F02E3" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE3_TYPE: "scene" # tile type: light | fan | switch | scene | script | cover | climate
TILE3_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE3_LONGPRESS: "none" # long press mode: none | slider | action
TILE3_VALUE_MODE: "auto" # value mode: auto | brightness | percentage | preset | text
TILE3_LABEL_OFF: "Uit" # text shown when entity is off
TILE3_LABEL_ON: "Aan" # text shown for text-mode tiles when entity is on
TILE3_ACTIVE_STATE: "" # optional: exact HA state that counts as "active" (empty = default "on")
# --- colors ---
TILE3_CIRCLE_ACTIVE_COLOR: "${ACCENT_AUTO}" # icon circle color when active
TILE3_CIRCLE_DISABLED_COLOR: "0x1B2D3C" # icon circle color when inactive
TILE3_ICON_ACTIVE_COLOR: "0xFFFFFF" # icon color when active
TILE3_ICON_DISABLED_COLOR: "${ACCENT_AUTO}" # icon color when inactive
TILE3_BG_ACTIVE_COLOR: "0xF5F3EB" # tile background color when active
TILE3_BG_DISABLED_COLOR: "0x293F50" # tile background color when inactive
TILE3_TITLE_ACTIVE_COLOR: "0x142334" # title color when active
TILE3_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
TILE3_VALUE_ACTIVE_COLOR: "0x526477" # value color when active
TILE3_VALUE_DISABLED_COLOR: "0xB9CDDB" # value color when inactive
# --- advanced settings ---
TILE3_TAP_SERVICE: "" # optional service override, e.g. light.toggle
TILE3_TAP_PARAM_KEY: "" # optional extra service parameter key
TILE3_TAP_PARAM_VAL: "" # optional extra service parameter value
# if LONGPRESS = slider:
TILE3_LONGPRESS_SLIDER: "auto" # slider type: auto | brightness | percentage | cover_position
TILE3_LONGPRESS_OFF_VALUE: "0" # initial slider value when entity is off
# if LONGPRESS = action:
TILE3_LONGPRESS_ACTION: "" # entity_id to fire on long press
TILE3_LONGPRESS_ACTION_TYPE: "" # entity type: light | fan | switch | scene | script | cover | climate (empty = same as TILE*_TYPE)
TILE3_LONGPRESS_ACTION_SERVICE: "" # service to call (default: activate/turn_on — set e.g. "light.toggle" to toggle instead)
# if TYPE = climate:
TILE3_CLIMATE_MIN_TEMP: "5.0" # minimum target temperature shown in the thermostat overlay
TILE3_CLIMATE_MAX_TEMP: "30.0" # maximum target temperature shown in the thermostat overlay
TILE3_CLIMATE_STEP: "0.5" # target temperature step size
TILE3_CLIMATE_CURRENT_TEMP_ATTRIBUTE: "current_temperature" # HA attribute for current room temperature
TILE3_CLIMATE_TARGET_TEMP_ATTRIBUTE: "temperature" # HA attribute for target temperature
TILE3_CLIMATE_HUMIDITY_ATTRIBUTE: "current_humidity" # HA attribute for humidity; leave as-is if unavailable
# ============================================================
# TILE 4
# ============================================================
# --- basic settings ---
TILE4_ENTITY: "script.voorbeeld_uit" # main Home Assistant entity for this tile
TILE4_STATE_ENTITY: "script.voorbeeld_uit" # entity used for active/inactive state styling
TILE4_TITLE: "Alles uit" # tile title shown in the UI
TILE4_ICON: "\U000F059C" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE4_TYPE: "script" # tile type: light | fan | switch | scene | script | cover | climate
TILE4_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE4_LONGPRESS: "none" # long press mode: none | slider | action
TILE4_VALUE_MODE: "auto" # value mode: auto | brightness | percentage | preset | text
TILE4_LABEL_OFF: "Uit" # text shown when entity is off
TILE4_LABEL_ON: "Aan" # text shown for text-mode tiles when entity is on
TILE4_ACTIVE_STATE: "" # optional: exact HA state that counts as "active" (empty = default "on")
# --- colors ---
TILE4_CIRCLE_ACTIVE_COLOR: "${ACCENT_AUTO}" # icon circle color when active
TILE4_CIRCLE_DISABLED_COLOR: "0x1B2D3C" # icon circle color when inactive
TILE4_ICON_ACTIVE_COLOR: "0xFFFFFF" # icon color when active
TILE4_ICON_DISABLED_COLOR: "${ACCENT_AUTO}" # icon color when inactive
TILE4_BG_ACTIVE_COLOR: "0xF5F3EB" # tile background color when active
TILE4_BG_DISABLED_COLOR: "0x293F50" # tile background color when inactive
TILE4_TITLE_ACTIVE_COLOR: "0x142334" # title color when active
TILE4_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
TILE4_VALUE_ACTIVE_COLOR: "0x526477" # value color when active
TILE4_VALUE_DISABLED_COLOR: "0xB9CDDB" # value color when inactive
# --- advanced settings ---
TILE4_TAP_SERVICE: "" # optional service override, e.g. light.toggle
TILE4_TAP_PARAM_KEY: "" # optional extra service parameter key
TILE4_TAP_PARAM_VAL: "" # optional extra service parameter value
# if LONGPRESS = slider:
TILE4_LONGPRESS_SLIDER: "auto" # slider type: auto | brightness | percentage | cover_position
TILE4_LONGPRESS_OFF_VALUE: "0" # initial slider value when entity is off
# if LONGPRESS = action:
TILE4_LONGPRESS_ACTION: "" # entity_id to fire on long press
TILE4_LONGPRESS_ACTION_TYPE: "" # entity type: light | fan | switch | scene | script | cover | climate (empty = same as TILE*_TYPE)
TILE4_LONGPRESS_ACTION_SERVICE: "" # service to call (default: activate/turn_on — set e.g. "light.toggle" to toggle instead)
# if TYPE = climate:
TILE4_CLIMATE_MIN_TEMP: "5.0" # minimum target temperature shown in the thermostat overlay
TILE4_CLIMATE_MAX_TEMP: "30.0" # maximum target temperature shown in the thermostat overlay
TILE4_CLIMATE_STEP: "0.5" # target temperature step size
TILE4_CLIMATE_CURRENT_TEMP_ATTRIBUTE: "current_temperature" # HA attribute for current room temperature
TILE4_CLIMATE_TARGET_TEMP_ATTRIBUTE: "temperature" # HA attribute for target temperature
TILE4_CLIMATE_HUMIDITY_ATTRIBUTE: "current_humidity" # HA attribute for humidity; leave as-is if unavailable
# ============================================================
# TILE 5
# ============================================================
# --- basic settings ---
TILE5_ENTITY: "climate.voorbeeld_airco" # main Home Assistant entity for this tile
TILE5_STATE_ENTITY: "climate.voorbeeld_airco" # entity used for active/inactive state styling
TILE5_TITLE: "Airco" # tile title shown in the UI
TILE5_ICON: "\U000F0425" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE5_TYPE: "climate" # tile type: light | fan | switch | scene | script | cover | climate
TILE5_TAP_ACTION: "toggle" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE5_LONGPRESS: "slider" # long press mode: none | slider | action
TILE5_VALUE_MODE: "auto" # value mode: auto | brightness | percentage | preset | text
TILE5_LABEL_OFF: "Uit" # text shown when entity is off (closed)
TILE5_LABEL_ON: "Aan" # text shown when entity is on (open)
TILE5_ACTIVE_STATE: "" # optional: exact HA state that counts as "active" (empty = default "on")
# --- colors ---
TILE5_CIRCLE_ACTIVE_COLOR: "${ACCENT_AUTO}" # icon circle color when active
TILE5_CIRCLE_DISABLED_COLOR: "0x1B2D3C" # icon circle color when inactive
TILE5_ICON_ACTIVE_COLOR: "0xFFFFFF" # icon color when active
TILE5_ICON_DISABLED_COLOR: "${ACCENT_AUTO}" # icon color when inactive
TILE5_BG_ACTIVE_COLOR: "0xF5F3EB" # tile background color when active
TILE5_BG_DISABLED_COLOR: "0x293F50" # tile background color when inactive
TILE5_TITLE_ACTIVE_COLOR: "0x142334" # title color when active
TILE5_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
TILE5_VALUE_ACTIVE_COLOR: "0x526477" # value color when active
TILE5_VALUE_DISABLED_COLOR: "0xB9CDDB" # value color when inactive
# --- advanced settings ---
TILE5_TAP_SERVICE: "climate.toggle" # optional service override, e.g. light.toggle
TILE5_TAP_PARAM_KEY: "" # optional extra service parameter key
TILE5_TAP_PARAM_VAL: "" # optional extra service parameter value
# if LONGPRESS = slider:
TILE5_LONGPRESS_SLIDER: "auto" # slider type: auto | brightness | percentage | cover_position
TILE5_LONGPRESS_OFF_VALUE: "0" # initial slider value when entity is off
# if LONGPRESS = action:
TILE5_LONGPRESS_ACTION: "" # entity_id to fire on long press
TILE5_LONGPRESS_ACTION_TYPE: "" # entity type: light | fan | switch | scene | script | cover | climate (empty = same as TILE*_TYPE)
TILE5_LONGPRESS_ACTION_SERVICE: "" # service to call (default: activate/turn_on — set e.g. "light.toggle" to toggle instead)
# if TYPE = climate:
TILE5_CLIMATE_MIN_TEMP: "5.0" # minimum target temperature shown in the thermostat overlay
TILE5_CLIMATE_MAX_TEMP: "30.0" # maximum target temperature shown in the thermostat overlay
TILE5_CLIMATE_STEP: "0.5" # target temperature step size
TILE5_CLIMATE_CURRENT_TEMP_ATTRIBUTE: "current_temperature" # HA attribute for current room temperature
TILE5_CLIMATE_TARGET_TEMP_ATTRIBUTE: "temperature" # HA attribute for target temperature
TILE5_CLIMATE_HUMIDITY_ATTRIBUTE: "current_humidity" # HA attribute for humidity; leave as-is if unavailable
# ============================================================
# TILE 6
# ============================================================
# --- basic settings ---
TILE6_ENTITY: "vacuum.voorbeeld_robot" # main Home Assistant entity for this tile
TILE6_STATE_ENTITY: "vacuum.voorbeeld_robot" # entity used for active/inactive state styling
TILE6_TITLE: "Stofzuiger" # tile title shown in the UI
TILE6_ICON: "\U000F070D" # MDI glyph — also add to MDI_GLYPH_* list above if not already there
TILE6_TYPE: "vacuum" # tile type: light | fan | switch | scene | script | cover | climate
TILE6_TAP_ACTION: "custom" # tap action: auto | toggle | activate | fan_toggle_preset | custom
TILE6_LONGPRESS: "vacuum" # long press mode: none | slider | action
TILE6_VALUE_MODE: "auto" # value mode: auto | brightness | percentage | preset | text
TILE6_LABEL_OFF: "Uit" # text shown when entity is off
TILE6_LABEL_ON: "Aan" # text shown for text-mode tiles when entity is on
TILE6_ACTIVE_STATE: "" # optional: exact HA state that counts as "active" (empty = default "on")
# --- colors ---
TILE6_CIRCLE_ACTIVE_COLOR: "${ACCENT_AUTO}" # icon circle color when active
TILE6_CIRCLE_DISABLED_COLOR: "0x1B2D3C" # icon circle color when inactive
TILE6_ICON_ACTIVE_COLOR: "0xFFFFFF" # icon color when active
TILE6_ICON_DISABLED_COLOR: "${ACCENT_AUTO}" # icon color when inactive
TILE6_BG_ACTIVE_COLOR: "0xF5F3EB" # tile background color when active
TILE6_BG_DISABLED_COLOR: "0x293F50" # tile background color when inactive
TILE6_TITLE_ACTIVE_COLOR: "0x142334" # title color when active
TILE6_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
TILE6_VALUE_ACTIVE_COLOR: "0x526477" # value color when active
TILE6_VALUE_DISABLED_COLOR: "0xB9CDDB" # value color when inactive
# --- advanced settings ---
TILE6_TAP_SERVICE: "vacuum.start" # optional service override, e.g. fan.toggle
TILE6_TAP_PARAM_KEY: "" # optional extra service parameter key
TILE6_TAP_PARAM_VAL: "" # optional extra service parameter value
# if LONGPRESS = slider:
TILE6_LONGPRESS_SLIDER: "auto" # slider type: auto | brightness | percentage | cover_position
TILE6_LONGPRESS_OFF_VALUE: "0" # initial slider value when entity is off
# if LONGPRESS = action:
TILE6_LONGPRESS_ACTION: "" # entity_id to fire on long press
TILE6_LONGPRESS_ACTION_TYPE: "" # entity type: light | fan | switch | scene | script | cover | climate (empty = same as TILE*_TYPE)
TILE6_LONGPRESS_ACTION_SERVICE: "" # service to call (default: activate/turn_on — set e.g. "light.toggle" to toggle instead)
# if TYPE = climate:
TILE6_CLIMATE_MIN_TEMP: "5.0" # minimum target temperature shown in the thermostat overlay
TILE6_CLIMATE_MAX_TEMP: "30.0" # maximum target temperature shown in the thermostat overlay
TILE6_CLIMATE_STEP: "0.5" # target temperature step size
TILE6_CLIMATE_CURRENT_TEMP_ATTRIBUTE: "current_temperature" # HA attribute for current room temperature
TILE6_CLIMATE_TARGET_TEMP_ATTRIBUTE: "temperature" # HA attribute for target temperature
TILE6_CLIMATE_HUMIDITY_ATTRIBUTE: "current_humidity" # HA attribute for humidity; leave as-is if unavailable
# ============================================================
# TILE 7 — Tafellamp
# Alleen-helderheid lamp (HA meldt supported_color_modes: ['brightness']),
# dus de kleurenknop in de slider-overlay blijft vanzelf verborgen.
# ============================================================
# --- basic settings ---
TILE7_ENTITY: "light.ongebruikt_7" # main Home Assistant entity for this tile
TILE7_STATE_ENTITY: "light.ongebruikt_7" # entity used for active/inactive state styling
TILE7_TITLE: "Tegel 7" # tile title shown in the UI
TILE7_ICON: "\U000F06B5" # mdi:lamp — also listed in MDI_GLYPH_7 above
TILE7_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE7_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE7_LONGPRESS: "none" # long press mode: none | slider | action | vacuum
TILE7_VALUE_MODE: "auto" # value mode: auto | brightness | percentage | preset | text
TILE7_LABEL_OFF: "Uit" # text shown when entity is off
TILE7_LABEL_ON: "Aan" # text shown for text-mode tiles when entity is on
TILE7_ACTIVE_STATE: "" # optional: exact HA state that counts as "active" (empty = default "on")
# --- colors ---
TILE7_CIRCLE_ACTIVE_COLOR: "${ACCENT_AUTO}" # icon circle color when active
TILE7_CIRCLE_DISABLED_COLOR: "0x1B2D3C" # icon circle color when inactive
TILE7_ICON_ACTIVE_COLOR: "0xFFFFFF" # icon color when active
TILE7_ICON_DISABLED_COLOR: "${ACCENT_AUTO}" # icon color when inactive
TILE7_BG_ACTIVE_COLOR: "0xF5F3EB" # tile background color when active
TILE7_BG_DISABLED_COLOR: "0x293F50" # tile background color when inactive
TILE7_TITLE_ACTIVE_COLOR: "0x142334" # title color when active
TILE7_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
TILE7_VALUE_ACTIVE_COLOR: "0x526477" # value color when active
TILE7_VALUE_DISABLED_COLOR: "0xB9CDDB" # value color when inactive
# --- advanced settings ---
TILE7_TAP_SERVICE: "" # optional service override, e.g. light.toggle
TILE7_TAP_PARAM_KEY: "" # optional extra service parameter key
TILE7_TAP_PARAM_VAL: "" # optional extra service parameter value
# if LONGPRESS = slider:
TILE7_LONGPRESS_SLIDER: "auto" # auto = brightness voor een light
TILE7_LONGPRESS_OFF_VALUE: "0" # initial slider value when entity is off
# if LONGPRESS = action:
TILE7_LONGPRESS_ACTION: "" # entity_id to fire on long press
TILE7_LONGPRESS_ACTION_TYPE: "" # entity type (empty = same as TILE*_TYPE)
TILE7_LONGPRESS_ACTION_SERVICE: "" # service to call (default: activate/turn_on)
# ============================================================
# TILE 8 — Brievenbus reset
# input_select.brievenbus_status heeft opties ['Vol', 'Leeg'].
# De tegel licht op zodra de status "Vol" is (ACTIVE_STATE), en een korte
# tik zet hem terug op "Leeg" via input_select.select_option.
# ============================================================
# --- basic settings ---
TILE8_ENTITY: "light.ongebruikt_8" # main Home Assistant entity for this tile
TILE8_STATE_ENTITY: "light.ongebruikt_8" # entity used for active/inactive state styling
TILE8_TITLE: "Tegel 8" # tile title shown in the UI
TILE8_ICON: "\U000F06EE" # mdi:mailbox — also listed in MDI_GLYPH_8 above
TILE8_TYPE: "light" # generiek type; het echte gedrag komt uit TAP_SERVICE hieronder
TILE8_TAP_ACTION: "auto" # activate + TAP_SERVICE = losse service call
TILE8_LONGPRESS: "none" # long press stuurt alleen het event naar HA
TILE8_VALUE_MODE: "auto" # toont LABEL_ON / LABEL_OFF
TILE8_LABEL_OFF: "Uit" # text shown when entity is off / inactive
TILE8_LABEL_ON: "Aan" # text shown when entity is active
TILE8_ACTIVE_STATE: "" # tegel is "aan" zodra de status exact "Vol" is
# --- colors ---
TILE8_CIRCLE_ACTIVE_COLOR: "0xE5484D" # rood zodra er post ligt
TILE8_CIRCLE_DISABLED_COLOR: "0x1B2D3C" # icon circle color when inactive
TILE8_ICON_ACTIVE_COLOR: "0xFFFFFF" # icon color when active
TILE8_ICON_DISABLED_COLOR: "0xE5484D" # icon color when inactive
TILE8_BG_ACTIVE_COLOR: "0xF5F3EB" # tile background color when active
TILE8_BG_DISABLED_COLOR: "0x293F50" # tile background color when inactive
TILE8_TITLE_ACTIVE_COLOR: "0x142334" # title color when active
TILE8_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
TILE8_VALUE_ACTIVE_COLOR: "0x526477" # value color when active
TILE8_VALUE_DISABLED_COLOR: "0xB9CDDB" # value color when inactive
# --- advanced settings ---
TILE8_TAP_SERVICE: "" # zet de input_select op een vaste optie
TILE8_TAP_PARAM_KEY: "" # parameternaam voor input_select.select_option
TILE8_TAP_PARAM_VAL: "" # de "reset"-waarde
# if LONGPRESS = slider:
TILE8_LONGPRESS_SLIDER: "auto" # niet in gebruik (LONGPRESS = none)
TILE8_LONGPRESS_OFF_VALUE: "0" # niet in gebruik
# if LONGPRESS = action:
TILE8_LONGPRESS_ACTION: "" # entity_id to fire on long press
TILE8_LONGPRESS_ACTION_TYPE: "" # entity type (empty = same as TILE*_TYPE)
TILE8_LONGPRESS_ACTION_SERVICE: "" # service to call (default: activate/turn_on)
# ============================================================
# TILE 9 — Woonkamer
# Verhuisd van tegel 2 naar rij 5 om plaats te maken voor Ambient.
# Kleurenlamp (color_temp / rgb / xy), dus de kleurknop in de
# slider-overlay verschijnt vanzelf.
# ============================================================
# --- basic settings ---
TILE9_ENTITY: "light.ongebruikt_9" # main Home Assistant entity for this tile
TILE9_STATE_ENTITY: "light.ongebruikt_9" # entity used for active/inactive state styling
TILE9_TITLE: "Tegel 9" # tile title shown in the UI
TILE9_ICON: "\U000F04B9" # mdi:sofa — also listed in MDI_GLYPH_9 above
TILE9_TYPE: "light" # tile type: light | fan | switch | scene | script | cover | climate
TILE9_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE9_LONGPRESS: "none" # long press mode: none | slider | action | vacuum
TILE9_VALUE_MODE: "auto" # value mode: auto | brightness | percentage | preset | text
TILE9_LABEL_OFF: "Uit" # text shown when entity is off
TILE9_LABEL_ON: "Aan" # text shown for text-mode tiles when entity is on
TILE9_ACTIVE_STATE: "" # optional: exact HA state that counts as "active" (empty = default "on")
# --- colors ---
TILE9_CIRCLE_ACTIVE_COLOR: "${ACCENT_AUTO}" # light -> ACCENT_LIGHT (amber)
TILE9_CIRCLE_DISABLED_COLOR: "0x1B2D3C" # icon circle color when inactive
TILE9_ICON_ACTIVE_COLOR: "0xFFFFFF" # icon color when active
TILE9_ICON_DISABLED_COLOR: "${ACCENT_AUTO}" # icon color when inactive
TILE9_BG_ACTIVE_COLOR: "0xF5F3EB" # tile background color when active
TILE9_BG_DISABLED_COLOR: "0x293F50" # tile background color when inactive
TILE9_TITLE_ACTIVE_COLOR: "0x142334" # title color when active
TILE9_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
TILE9_VALUE_ACTIVE_COLOR: "0x526477" # value color when active
TILE9_VALUE_DISABLED_COLOR: "0xB9CDDB" # value color when inactive
# --- advanced settings ---
TILE9_TAP_SERVICE: "" # optional service override, e.g. light.toggle
TILE9_TAP_PARAM_KEY: "" # optional extra service parameter key
TILE9_TAP_PARAM_VAL: "" # optional extra service parameter value
# if LONGPRESS = slider:
TILE9_LONGPRESS_SLIDER: "auto" # auto = brightness voor een light
TILE9_LONGPRESS_OFF_VALUE: "0" # initial slider value when entity is off
# if LONGPRESS = action:
TILE9_LONGPRESS_ACTION: "" # entity_id to fire on long press
TILE9_LONGPRESS_ACTION_TYPE: "" # entity type (empty = same as TILE*_TYPE)
TILE9_LONGPRESS_ACTION_SERVICE: "" # service to call (default: activate/turn_on)
# ============================================================
# TILE 10 — Alarm aan/uit
# input_boolean.alarm_engaged. Een input_boolean reageert niet op
# switch.toggle, vandaar de expliciete TAP_SERVICE.
# ============================================================
# --- basic settings ---
TILE10_ENTITY: "light.ongebruikt_10" # main Home Assistant entity for this tile
TILE10_STATE_ENTITY: "light.ongebruikt_10" # entity used for active/inactive state styling
TILE10_TITLE: "Tegel 10" # tile title shown in the UI
TILE10_ICON: "\U000F002A" # mdi:alarm-light — also listed in MDI_GLYPH_10 above
TILE10_TYPE: "light" # generiek aan/uit-type; de service hieronder doet het echte werk
TILE10_TAP_ACTION: "auto" # tap action: auto | toggle | activate | fan_toggle_preset
TILE10_LONGPRESS: "none" # long press stuurt alleen het event naar HA
TILE10_VALUE_MODE: "auto" # toont LABEL_ON / LABEL_OFF
TILE10_LABEL_OFF: "Uit" # text shown when entity is off
TILE10_LABEL_ON: "Aan" # text shown when entity is on
TILE10_ACTIVE_STATE: "" # input_boolean gebruikt gewoon on/off
# --- colors ---
TILE10_CIRCLE_ACTIVE_COLOR: "${ACCENT_AUTO}" # input_boolean -> ACCENT_DEVICE (cyaan)
TILE10_CIRCLE_DISABLED_COLOR: "0x1B2D3C" # icon circle color when inactive
TILE10_ICON_ACTIVE_COLOR: "0xFFFFFF" # icon color when active
TILE10_ICON_DISABLED_COLOR: "${ACCENT_AUTO}" # icon color when inactive
TILE10_BG_ACTIVE_COLOR: "0xF5F3EB" # tile background color when active
TILE10_BG_DISABLED_COLOR: "0x293F50" # tile background color when inactive
TILE10_TITLE_ACTIVE_COLOR: "0x142334" # title color when active
TILE10_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
TILE10_VALUE_ACTIVE_COLOR: "0x526477" # value color when active
TILE10_VALUE_DISABLED_COLOR: "0xB9CDDB" # value color when inactive
# --- advanced settings ---
TILE10_TAP_SERVICE: "" # switch.toggle werkt niet op een input_boolean
TILE10_TAP_PARAM_KEY: "" # optional extra service parameter key
TILE10_TAP_PARAM_VAL: "" # optional extra service parameter value
# if LONGPRESS = slider:
TILE10_LONGPRESS_SLIDER: "auto" # niet in gebruik (LONGPRESS = none)
TILE10_LONGPRESS_OFF_VALUE: "0" # niet in gebruik
# if LONGPRESS = action:
TILE10_LONGPRESS_ACTION: "" # entity_id to fire on long press
TILE10_LONGPRESS_ACTION_TYPE: "" # entity type (empty = same as TILE*_TYPE)
TILE10_LONGPRESS_ACTION_SERVICE: "" # service to call (default: activate/turn_on)
# Square 480x480 layout; GT911 coordinates are already in pixels.
ORIENTATION: "0"
DISPLAY_W: "480"
DISPLAY_H: "480"
LVGL_ROTATION: "0"
TOUCH_SWAP_XY: "false"
TOUCH_MIRROR_X: "true"
TOUCH_MIRROR_Y: "true"
GRID_COL1_X: "16"
GRID_COL2_X: "246"
GRID_ROW1_Y: "0"
GRID_ROW2_Y: "120"
GRID_ROW3_Y: "240"
GRID_ROW4_Y: "360"
GRID_ROW5_Y: "480"
SCROLL_Y: "72"
SCROLL_H: "348"
TILE_W: "218"
TILE_H: "108"
HEADER_Y: "22"
OVERLAY_SLIDER_W: "144"
OVERLAY_SLIDER_H: "300"
OVERLAY_ICON_Y: "148"
BG_TOP_COLOR: "0x111D2B"
BG_BOTTOM_COLOR: "0x28485A"
TILE_ICON_X: "0"
TILE_ICON_Y: "12"
TILE_TITLE_X: "64"
TILE_TITLE_Y: "17"
TILE_VALUE_X: "64"
TILE_VALUE_Y: "45"
# ============================================================
# No need to edit below this line.
# All configuration is done via the substitutions above.
# Credentials are managed in secrets.yaml (see secrets.yaml.example).
# The Material Design Icons font is included at fonts/materialdesignicons-webfont.ttf (Apache 2.0).
# Source: https://github.com/Templarian/MaterialDesign-Webfont
# ============================================================
external_components:
- source:
type: git
url: https://github.com/MaxGramser/homeassistant_espscreen.git
ref: main
path: components
refresh: 0s
components: [smart_display]
esphome:
min_version: 2026.6.2
project:
name: maxgramser.esp_screen
version: "0.1.0"
platformio_options:
build_flags:
- -DLV_USE_SNAPSHOT=1
includes:
- <esphome/components/smart_display/cyd_ui.h>
- <esphome/components/smart_display/light_controls.h>
- <esphome/components/smart_display/guition_diagnostics.h>
name: ${DEVICE_NAME}
friendly_name: ${DEVICE_FRIENDLY_NAME}
on_boot:
priority: -100
then:
- lambda: |-
id(last_touch_ms) = millis();
id(display_dimmed) = false;
lv_obj_set_style_pad_row(id(vacuum_actions), 4, 0);
lv_obj_set_style_pad_column(id(vacuum_actions), 4, 0);
lv_obj_set_style_pad_row(id(vacuum_speeds), 4, 0);
lv_obj_set_style_pad_column(id(vacuum_speeds), 4, 0);
- lambda: |-
runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true";
if (!runtime_tiles::enabled) return;
runtime_tiles::dismiss = []() {
id(active_entity).clear();
lv_obj_add_flag(id(brightness_overlay), LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(id(color_detail_overlay), LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(id(climate_detail_overlay), LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(id(climate_mode_overlay), LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(id(vacuum_overlay), LV_OBJ_FLAG_HIDDEN);
};
runtime_tiles::layout_changed = []() { id(show_tile_page).execute(); };
runtime_tiles::refresh = []() { if (!id(ui_refresh).is_running()) id(ui_refresh).execute(); };
runtime_tiles::bind(0, id(tile1), id(t1_title), id(t1_value), id(tile1_icon_circle), id(tile1_icon_lbl));
runtime_tiles::bind(1, id(tile2), id(t2_title), id(t2_value), id(tile2_icon_circle), id(tile2_icon_lbl));
runtime_tiles::bind(2, id(tile3), id(t3_title), id(t3_value), id(tile3_icon_circle), id(tile3_icon_lbl));
runtime_tiles::bind(3, id(tile4), id(t4_title), id(t4_value), id(tile4_icon_circle), id(tile4_icon_lbl));
runtime_tiles::bind(4, id(tile5), id(t5_title), id(t5_value), id(tile5_icon_circle), id(tile5_icon_lbl));
runtime_tiles::bind(5, id(tile6), id(t6_title), id(t6_value), id(tile6_icon_circle), id(tile6_icon_lbl));
runtime_tiles::bind(6, id(tile7), id(t7_title), id(t7_value), id(tile7_icon_circle), id(tile7_icon_lbl));
runtime_tiles::bind(7, id(tile8), id(t8_title), id(t8_value), id(tile8_icon_circle), id(tile8_icon_lbl));
runtime_tiles::bind(8, id(tile9), id(t9_title), id(t9_value), id(tile9_icon_circle), id(tile9_icon_lbl));
runtime_tiles::bind(9, id(tile10), id(t10_title), id(t10_value), id(tile10_icon_circle), id(tile10_icon_lbl));
runtime_tiles::detail = [](runtime_tiles::Tile &t) {
if (t.domain() == "vacuum") {
lv_obj_t *buttons[] = {id(vacspd_quiet), id(vacspd_balanced), id(vacspd_turbo), id(vacspd_max)};
for (unsigned i = 0; i < 4; ++i) {
if (i < t.fan_speed_count) {
lv_obj_clear_flag(buttons[i], LV_OBJ_FLAG_HIDDEN);
lv_label_set_text(lv_obj_get_child(buttons[i], 0), t.fan_speeds[i].c_str());
} else lv_obj_add_flag(buttons[i], LV_OBJ_FLAG_HIDDEN);
}
id(open_vacuum_overlay).execute(t.name, t.entity);
return;
}
light_controls::fallback = light_controls::State{};
light_controls::fallback.hue = t.hue;
light_controls::fallback.kelvin = t.kelvin;
light_controls::fallback.minimum = t.min_kelvin;
light_controls::fallback.maximum = t.max_kelvin;
id(open_value_overlay).execute(t.name, t.entity, t.domain(), "auto", t.active(), 0,
t.brightness, t.percentage, t.position, runtime_tiles::icon_for(t),
0x565C66, 0xE4B23C, 0xFFFFFF, "Uit", "Aan", t.modes,
t.current, t.target, t.humidity, t.state, t.hvac_modes,
std::to_string(t.minimum), std::to_string(t.maximum), std::to_string(t.step));
};
runtime_tiles::detail_update = [](runtime_tiles::Tile &t) {
if (id(active_entity) != t.entity || id(touch_down)) return;
if (t.domain() == "climate" && !lv_obj_has_flag(id(climate_detail_overlay), LV_OBJ_FLAG_HIDDEN)) {
if (id(climate_edit_pending) && millis() - id(climate_edit_ms) < 3000) return;
id(climate_edit_pending) = false;
id(active_climate_mode) = t.state;
id(active_climate_supported_modes) = t.hvac_modes;
lv_label_set_text(id(climate_mode_label), t.state.c_str());
char buf[72];
if (std::isfinite(t.current)) snprintf(buf, sizeof(buf), "Nu: %.1f°", t.current);
else snprintf(buf, sizeof(buf), "Nu: --");
lv_label_set_text(id(climate_current_label), buf);
if (std::isfinite(t.target)) {
int target = cyd::quantize_temperature(lround(t.target * 10), id(active_climate_min_x10), id(active_climate_max_x10), id(active_climate_step_x10));
id(active_climate_target_x10) = target;
snprintf(buf, sizeof(buf), "%.1f°", target / 10.0f);
lv_label_set_text(id(climate_target_label), buf);
lv_arc_set_value(id(climate_arc), target);
}
}
};
- lambda: |-
static const char *entities[] = {"${TILE1_ENTITY}", "${TILE2_ENTITY}", "${TILE3_ENTITY}", "${TILE4_ENTITY}", "${TILE5_ENTITY}", "${TILE6_ENTITY}", "${TILE7_ENTITY}", "${TILE9_ENTITY}"};
if (!runtime_tiles::enabled) light_controls::subscribe(entities, 8);
light_controls::setup(id(color_detail_overlay), id(sublabel)->get_lv_font(), atoi("${DISPLAY_W}"), atoi("${DISPLAY_H}"));
light_controls::commits[0] = [](int value) { id(set_light_hue).execute(value); };
light_controls::commits[1] = [](int value) { id(set_light_color_temperature).execute(value); };
light_controls::commits[2] = [](int value) { id(color_brightness_commit).execute(value); };
- light.turn_on:
id: back_light
brightness: !lambda "return atoi(\"${MAX_BRIGHTNESS}\") / 100.0f;"
- script.execute: show_tile_page
- lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();'
- if:
condition:
lambda: 'return std::string("${CALIBRATION_ON_BOOT}") == "true";'
then:
- lambda: 'id(calibration_active) = true;'
- lvgl.page.show: touch_cal_page
- lambda: |-
lv_obj_invalidate(id(touch_cal_page)->obj);
lv_refr_now(nullptr);
ESP_LOGI("ui_diag", "GT911 touch test ready; no tile actions");
esp32:
board: esp32-s3-devkitc-1
variant: esp32s3
flash_size: 16MB
cpu_frequency: 240MHz
framework:
type: esp-idf
version: recommended
advanced:
execute_from_psram: true
sdkconfig_options:
CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
CONFIG_LCD_RGB_RESTART_IN_VSYNC: "y"
psram:
mode: octal
speed: 80MHz
logger:
tx_buffer_size: 2048
hardware_uart: UART0
# Voor diagnose tijdelijk op DEBUG of VERBOSE zetten. Let op: ESPHome logt
# zijn configuratieregels op niveau CONFIG, die zijn op INFO onzichtbaar.
level: DEBUG
logs:
sensor: WARN
text_sensor: WARN
xpt2046: WARN
homeassistant.sensor: WARN
homeassistant.text_sensor: WARN
# Credentials are loaded from secrets.yaml — copy secrets.yaml.example to secrets.yaml and fill in your values.
api:
actions:
- action: ui_snapshot
then:
- script.execute: capture_ui_snapshot
- action: touch_diagnostics
then:
- lambda: 'id(calibration_active) = true;'
- script.execute: wake_display
- lvgl.page.show: touch_cal_page
- lambda: |-
lv_obj_invalidate(id(touch_cal_page)->obj);
lv_refr_now(nullptr);
ESP_LOGI("ui_diag", "calibration page active=%d", lv_screen_active() == id(touch_cal_page)->obj);
- action: end_touch_diagnostics
then:
- lambda: 'id(calibration_active) = false;'
- lvgl.page.show: home_page
- action: ui_state
then:
- lambda: |-
lv_area_t nav, grid;
lv_obj_update_layout(id(home_page)->obj);
lv_obj_get_coords(id(page_next), &nav);
lv_obj_get_coords(id(tile_scroll), &grid);
ESP_LOGI("ui_diag", "home=%d calibration=%d grid=%d,%d,%d,%d next=%d,%d,%d,%d",
lv_screen_active() == id(home_page)->obj, lv_screen_active() == id(touch_cal_page)->obj,
grid.x1, grid.y1, grid.x2, grid.y2, nav.x1, nav.y1, nav.x2, nav.y2);
- action: light_controls_preview
then:
- script.execute: light_controls_preview
- action: ui_self_test
then:
- script.execute: ui_self_test
captive_portal:
interval:
- interval: 1s
then:
- if:
condition:
lambda: 'return id(climate_edit_pending) && !id(touch_down) && millis() - id(climate_edit_ms) >= 3000;'
then:
- lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();'
- if:
condition:
lambda: |-
if (std::string("${AUTO_DIM}") != "true") return false;
if (id(display_dimmed) || id(touch_down) || id(calibration_active)) return false;
return (millis() - id(last_touch_ms)) > (atoi("${AUTO_DIM_TIMEOUT}") * 1000UL);
then:
- script.execute: dim_display
time:
- platform: homeassistant
id: homeassistant_time
# Refresh immediately on time sync; do not wait for an HA entity update.
update_interval: 60s
on_time_sync:
then:
- lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();'
on_time:
- seconds: 0
minutes: /1
then:
- lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();'
- seconds: 0
minutes: 0
hours: ${NIGHT_START_HOUR}
then:
- if:
condition:
lambda: 'return id(display_dimmed);'
then:
- script.execute: dim_display
- seconds: 0
minutes: 0
hours: ${NIGHT_END_HOUR}
then:
- if:
condition:
lambda: 'return id(display_dimmed);'
then:
- script.execute: dim_display
globals:
- id: rendered_frames
type: uint32_t
initial_value: '0'
- id: calibration_active
type: bool
initial_value: 'false'
- id: tile_page
type: int
initial_value: '0'
- id: touch_down
type: bool
initial_value: 'false'
- id: climate_edit_ms
type: uint32_t
initial_value: '0'
- id: climate_edit_pending
type: bool
initial_value: 'false'
- id: active_entity
type: std::string
restore_value: no
- id: active_control_kind
type: std::string
restore_value: no
- id: active_value_suffix
type: std::string
restore_value: no
- id: last_touch_ms
type: uint32_t
restore_value: no
initial_value: '0'
# Debounce voor do_tile_action: bescherm tegen herhaalde events bij
# meerdere losse press/release-cycli voor één fysieke aanraking (ruis in
# het weerstandslaagje), waardoor dezelfde HA-actie meerdere keren binnen
# een paar honderd ms vuurt (gezien: script.ambient_morning 6x in 8s).
- id: last_action_entity
type: std::string
restore_value: no
- id: last_action_ms
type: uint32_t
restore_value: no
initial_value: '0'
- id: display_dimmed
type: bool
restore_value: no
initial_value: 'false'
- id: active_label_off
type: std::string
restore_value: no
- id: active_label_on
type: std::string
restore_value: no
- id: active_title
type: std::string
restore_value: no
- id: active_icon_text
type: std::string
restore_value: no
- id: active_brightness_pct
type: int
restore_value: no
initial_value: '100'
- id: active_supports_color
type: bool
restore_value: no
initial_value: 'false'
- id: active_supports_temperature
type: bool
restore_value: no
initial_value: 'false'
- id: active_color_detail_tab
type: int
restore_value: no
initial_value: '0'
- id: active_climate_current_x10
type: int
restore_value: no
initial_value: '0'
- id: active_climate_target_x10
type: int
restore_value: no
initial_value: '200'
- id: active_climate_humidity
type: int
restore_value: no
initial_value: '-1'
- id: active_climate_min_x10
type: int
restore_value: no
initial_value: '50'
- id: active_climate_max_x10
type: int
restore_value: no
initial_value: '300'
- id: active_climate_step_x10
type: int
restore_value: no
initial_value: '5'
- id: active_climate_mode
type: std::string
restore_value: no
- id: active_climate_supported_modes
type: std::string
restore_value: no
font:
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/Roboto-700.ttf"
id: climate_number
size: 46
bpp: 4
glyphs: "0123456789.-° "
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/Roboto-500.ttf"
id: time_label
size: 27
bpp: 4
glyphs: ['0','1','2','3','4','5','6','7','8','9',':',' ', 'A', 'M', 'P']
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/Roboto-500.ttf"
id: headline
size: 27
bpp: 4
glyphs: ['<','>','—','&','@','!',',','.','?','"','%','(',')','+','-','_',':','°',
'0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'ä','ö','ü','Ä','Ö','Ü','ß','/']
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/Roboto-700.ttf"
id: label
size: 18
bpp: 4
glyphs: ['<','>','—','&','@','!',',','.','?','"','%','(',')','+','-','_',':','°',
'0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'ä','ö','ü','Ä','Ö','Ü','ß','/']
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/Roboto-400.ttf"
id: sublabel
size: 16
bpp: 4
glyphs: ['<','>','—','&','@','!',',','.','?','"','%','(',')','+','-','_',':','°',
'0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'ä','ö','ü','Ä','Ö','Ü','ß','/','…']
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/Roboto-400.ttf"
id: sublabel_big
size: 21
bpp: 4
glyphs: ['<','>','—','&','@','!',',','.','?','"','%','(',')','+','-','_',':','°',
'0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'ä','ö','ü','Ä','Ö','Ü','ß','/','…']
# Material Design Icons (MDI) — included at fonts/materialdesignicons-webfont.ttf (Apache 2.0)
# Source: https://github.com/Templarian/MaterialDesign-Webfont
- file: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts/materialdesignicons-webfont.ttf"
id: materialdesign_icons
size: 42
glyphs:
- "\U000F0335"
- "\U000F0210"
- "\U000F111C"
- "\U000F029A"
- "${MDI_GLYPH_1}"
- "${MDI_GLYPH_2}"
- "${MDI_GLYPH_3}"
- "${MDI_GLYPH_4}"
- "${MDI_GLYPH_5}"
- "${MDI_GLYPH_6}"
- "${MDI_GLYPH_7}"
- "${MDI_GLYPH_8}"
- "${MDI_GLYPH_9}"
- "${MDI_GLYPH_10}"
- "\U000F012C" # check
# Geheugen zichtbaar maken: dit bord heeft geen PSRAM, dus de vrije heap
# bepaalt hoeveel UI erbij kan. Verschijnt ook als entity in Home Assistant.
debug:
update_interval: 30s
sensor:
- platform: debug
free:
name: "Heap Free"
id: heap_free
on_value:
- lambda: 'ESP_LOGI("health", "free_heap=%.0f", x);'
fragmentation:
name: "Heap Fragmentation"
block:
name: "Heap Largest Block"
on_value:
- lambda: 'ESP_LOGI("health", "largest_block=%.0f", x);'
- platform: wifi_signal
name: "Wifi Signal"
update_interval: 600s
- platform: uptime
name: "Uptime"
id: uptime_s
update_interval: 15s
- platform: template
update_interval: never
id: tile1_brightness
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile1_percentage
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile2_brightness
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile2_percentage
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile3_brightness
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile3_percentage
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile4_brightness
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile4_percentage
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile5_brightness
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile5_percentage
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile6_brightness
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile6_percentage
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
# Tegel 7 (Tafellamp) is een alleen-helderheid light: alleen brightness.
# Bewust geen percentage / cover_position / climate-sensors — elke
# HA-subscription kost heap op een bord zonder PSRAM.
- platform: template
update_interval: never
id: tile7_brightness
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
# Tegel 9 (Woonkamer) is een kleurenlamp: brightness + supported_color_modes.
# Geen percentage / cover / climate — die leest deze tegel nooit.
- platform: template
update_interval: never
id: tile9_brightness
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile1_cover_position
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile2_cover_position
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile3_cover_position
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile4_cover_position
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile5_cover_position
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile6_cover_position
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile1_climate_current_temperature
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile1_climate_target_temperature
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile1_climate_humidity
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile2_climate_current_temperature
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile2_climate_target_temperature
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile2_climate_humidity
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile3_climate_current_temperature
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile3_climate_target_temperature
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile3_climate_humidity
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile4_climate_current_temperature
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile4_climate_target_temperature
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile4_climate_humidity
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile5_climate_current_temperature
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile5_climate_target_temperature
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile5_climate_humidity
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile6_climate_current_temperature
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile6_climate_target_temperature
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile6_climate_humidity
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
binary_sensor:
- platform: status
name: "Node Status"
id: system_status
# ----------------------------------------------------------
# Text sensors
#
# smartdisplay_action:
# - Main touch event channel exported to Home Assistant
# - On short tap, publishes one of:
# tile1_press / tile2_press / tile3_press / tile4_press / tile5_press / tile6_press
# - The value is reset shortly after publishing
#
# tile*_preset:
# - Mirrors the Home Assistant preset_mode attribute for each tile entity
# - Used by the UI to display fan presets when applicable
# ----------------------------------------------------------
text_sensor:
- platform: template
name: "Schermfirmware"
entity_category: diagnostic
lambda: 'return {"0.1.0"};'
update_interval: 60s
- platform: template
id: smartdisplay_action
name: "SmartDisplay Action"
icon: mdi:gesture-tap-button
- platform: template
update_interval: never
id: ha_state_tile1
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: ha_state_tile2
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: ha_state_tile3
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: ha_state_tile4
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: ha_state_tile5
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: ha_state_tile6
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: ha_state_tile7
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: ha_state_tile8
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: ha_state_tile9
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: ha_state_tile10
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
# Woonkamer meldt color_temp/rgb/xy, dus hier verschijnt de kleurknop wel.
- platform: template
update_interval: never
id: tile9_supported_color_modes
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
# Gebruikt door de slider-overlay om te bepalen of de kleurknop nodig is.
# Deze lamp meldt ['brightness'], dus de knop blijft vanzelf verborgen.
- platform: template
update_interval: never
id: tile7_supported_color_modes
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile1_preset
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile2_preset
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile3_preset
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile4_preset
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile5_preset
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile6_preset
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile1_supported_color_modes
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile2_supported_color_modes
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile3_supported_color_modes
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile4_supported_color_modes
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile5_supported_color_modes
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile6_supported_color_modes
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile1_climate_mode
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile2_climate_mode
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile3_climate_mode
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile4_climate_mode
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile5_climate_mode
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile6_climate_mode
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile1_climate_supported_modes
- platform: template
update_interval: never
id: tile2_climate_supported_modes
- platform: template
update_interval: never
id: tile3_climate_supported_modes
- platform: template
update_interval: never
id: tile4_climate_supported_modes
- platform: template
update_interval: never
id: tile5_climate_supported_modes
- platform: template
update_interval: never
id: tile6_climate_supported_modes
- platform: template
update_interval: never
id: tile1_cover_state
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile2_cover_state
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile3_cover_state
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile4_cover_state
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile5_cover_state
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
- platform: template
update_interval: never
id: tile6_cover_state
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
switch:
- platform: restart
name: "Restart"
# Two SPI buses used by the CYD:
# - lcd: built-in display
# - my_touchscreen: built-in touch controller
# Pinout from ESPHome's GUITION-4848S040 preset. No relay outputs are configured.
spi:
id: lcd
clk_pin: GPIO48
mosi_pin: GPIO47
i2c:
id: touch_bus
sda: GPIO19
scl:
number: GPIO45
ignore_strapping_warning: true
scan: true
output:
- platform: ledc
pin: GPIO38
id: gpio_backlight_pwm
frequency: 20000Hz
light:
- platform: monochromatic
output: gpio_backlight_pwm
name: "Display Backlight"
id: back_light
restore_mode: ALWAYS_ON
default_transition_length: 150ms
touchscreen:
platform: gt911
id: ts_touch
i2c_id: touch_bus
display: my_display
update_interval: 20ms
transform:
swap_xy: ${TOUCH_SWAP_XY}
mirror_x: ${TOUCH_MIRROR_X}
mirror_y: ${TOUCH_MIRROR_Y}
on_touch:
- lambda: |-
id(last_touch_ms) = millis();
id(touch_down) = true;
cyd::touch_guard.begin(millis(), touch.x, touch.y);
ESP_LOGI("touch", "GT911 press x=%u y=%u test=%d", touch.x, touch.y, id(calibration_active));
on_update:
- lambda: |-
id(last_touch_ms) = millis();
for (const auto &point : touches) {
cyd::touch_guard.update(point.x, point.y);
}
on_release:
- lambda: |-
id(last_touch_ms) = millis();
id(touch_down) = false;
display:
- platform: mipi_rgb
id: my_display
model: GUITION-4848S040
# 16MHz reduces visible low-refresh flicker on this physical 480x480 panel.
pclk_frequency: 16MHz
spi_id: lcd
update_interval: never
auto_clear_enabled: false
lvgl:
on_draw_start:
- lambda: 'id(rendered_frames)++;'
displays:
- my_display
touchscreens:
- touchscreen_id: ts_touch
# S3 octal PSRAM holds RGB framebuffer and LVGL draw buffers.
buffer_size: 25%
# Directe lokale feedback op de HELE UI, niet alleen op de tegels.
# Een thema zet de stijl per widget-type, dus dit dekt in een klap alle
# knoppen, tabs, sliders en de thermostaatring. Niet-klikbare widgets
# komen nooit in de pressed-state, dus die blijven onaangeroerd.
theme:
obj:
pressed:
# bg_opa en NIET opa: `opa` op widget-niveau dwingt LVGL het element
# naar een aparte laag te renderen, en die laag kost tientallen KB's
# per ingedrukte widget. Dat liep hier het geheugen uit.
bg_opa: 45%
slider:
knob:
pressed:
bg_opa: 65%
arc:
knob:
pressed:
bg_opa: 65%
# LVGL owns the rotation (display + touch) since ESPHome 2026.4.
rotation: ${LVGL_ROTATION}
style_definitions:
# Achtergrond: twee-stops verticale gradient in plaats van de 320x240
# RGB565-wallpaper. Scheelt ~150 KB flash en een image-blit per hertekening,
# wat je direct terugziet in de vloeiendheid van het scrollen.
- id: style_bg_grad
bg_color: ${BG_TOP_COLOR}
bg_grad_color: ${BG_BOTTOM_COLOR}
bg_grad_dir: VER
bg_opa: COVER
border_width: 0
radius: 0
- id: style_tile
bg_color: 0xFFFFFF
bg_opa: COVER
radius: 22
border_width: 0
pad_left: 12
pad_right: 12
pad_top: 12
pad_bottom: 12
shadow_width: 0
shadow_opa: 40%
- id: style_tile_disabled
bg_color: 0x293F50
bg_opa: COVER
radius: 22
border_width: 0
pad_left: 12
pad_right: 12
pad_top: 12
pad_bottom: 12
shadow_width: 0
shadow_opa: 25%
- id: style_icon_circle
bg_color: 0xFEC600
bg_opa: COVER
radius: 999
border_width: 0
- id: style_icon_circle_disabled
bg_color: 0x7B7B6F
bg_opa: COVER
radius: 999
border_width: 0
- id: style_room
text_font: headline
text_color: 0xFFFFFF
text_opa: 100%
- id: style_time
text_font: time_label
text_color: 0xFFFFFF
text_opa: 100%
- id: style_title
text_font: label
text_color: 0x000000
text_opa: 100%
- id: style_value
text_font: sublabel
text_color: 0x7A7A7C
text_opa: 100%
- id: style_value_big
text_font: sublabel_big
text_color: 0xFFFFFF
text_opa: 80%
- id: style_title_disabled
text_font: label
text_color: 0xFFFFFF
text_opa: 100%
- id: style_value_disabled
text_font: sublabel
text_color: 0xD9D9D9
text_opa: 100%
pages:
- id: home_page
styles: style_bg_grad
pad_all: 0
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
id: lbl_room
x: 16
y: ${HEADER_Y}
text: "${ROOM_NAME}"
styles: style_room
- label:
id: lbl_time
align: TOP_RIGHT
x: -16
y: ${HEADER_Y}
text: ""
styles: style_time
# Six tiles on page 1, four on page 2. Fixed pages keep navigation predictable.
- obj:
id: tile_scroll
x: 0
y: ${SCROLL_Y}
width: ${DISPLAY_W}
height: ${SCROLL_H}
bg_opa: TRANSP
border_width: 0
pad_left: 0
pad_right: 0
pad_top: 0
pad_bottom: 0
scrollable: false
scrollbar_mode: 'OFF'
widgets:
# ---------- TILE 1 ----------
- obj:
id: tile1
x: ${GRID_COL1_X}
y: ${GRID_ROW1_Y}
width: ${TILE_W}
height: ${TILE_H}
styles: style_tile
scrollable: false
scrollbar_mode: 'OFF'
# Directe lokale feedback: LVGL past dit toe zodra de vinger het
# glas raakt, in dezelfde frame. Onafhankelijk van Home Assistant.
pressed:
bg_opa: 45%
widgets:
- obj:
id: tile1_icon_circle
x: ${TILE_ICON_X}
y: ${TILE_ICON_Y}
width: 54
height: 54
styles: style_icon_circle
clickable: false
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
id: tile1_icon_lbl
align: CENTER
text_font: materialdesign_icons
text_color: 0xFFFFFF
text: "${TILE1_ICON}"
clickable: false
- label:
id: t1_title
width: 130
long_mode: DOT
x: ${TILE_TITLE_X}
y: ${TILE_TITLE_Y}
text: "${TILE1_TITLE}"
styles: style_title
clickable: false
- label:
id: t1_value
width: 130
long_mode: DOT
x: ${TILE_VALUE_X}
y: ${TILE_VALUE_Y}
text: "—"
styles: style_value
clickable: false
on_short_click:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 1);'
then:
- script.execute: { id: publish_action, action: "tile1_press" }
- script.execute:
id: do_tile_action
tile_type: "${TILE1_TYPE}"
tap_action: "${TILE1_TAP_ACTION}"
service: "${TILE1_TAP_SERVICE}"
entity_id: "${TILE1_ENTITY}"
param_key: "${TILE1_TAP_PARAM_KEY}"
param_val: "${TILE1_TAP_PARAM_VAL}"
was_on: !lambda |-
if (std::string("${TILE1_TYPE}") == "cover")
return id(tile1_cover_state).state != "closed";
return std::string(id(ha_state_tile1).state.c_str()) == "on";
on_long_press:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 1);'
then:
- script.execute: { id: publish_action, action: "tile1_long_press" }
- if:
condition:
lambda: "return std::string(\"${TILE1_LONGPRESS}\") == \"action\";"
then:
- script.execute:
id: do_tile_action
tile_type: !lambda |-
std::string t = "${TILE1_LONGPRESS_ACTION_TYPE}";
return t.empty() ? std::string("${TILE1_TYPE}") : t;
tap_action: "activate"
service: "${TILE1_LONGPRESS_ACTION_SERVICE}"
entity_id: "${TILE1_LONGPRESS_ACTION}"
param_key: ""
param_val: ""
was_on: false
else:
- if:
condition:
lambda: "return std::string(\"${TILE1_LONGPRESS}\") == \"slider\";"
then:
- script.execute:
id: open_value_overlay
title: "${TILE1_TITLE}"
entity: "${TILE1_ENTITY}"
tile_type: "${TILE1_TYPE}"
longpress_mode: "${TILE1_LONGPRESS_SLIDER}"
was_on: !lambda |-
if (std::string("${TILE1_TYPE}") == "cover")
return id(tile1_cover_state).state != "closed";
return std::string(id(ha_state_tile1).state.c_str()) == "on";
off_value: !lambda "return atoi(\"${TILE1_LONGPRESS_OFF_VALUE}\");"
brightness_value: !lambda "return id(tile1_brightness).state;"
percentage_value: !lambda "return id(tile1_percentage).state;"
position_value: !lambda "return id(tile1_cover_position).state;"
icon_text: "${TILE1_ICON}"
slider_bg_color: ${TILE1_BG_DISABLED_COLOR}
slider_fill_color: ${TILE1_CIRCLE_ACTIVE_COLOR}
slider_icon_color: ${TILE1_ICON_ACTIVE_COLOR}
label_off: "${TILE1_LABEL_OFF}"
label_on: "${TILE1_LABEL_ON}"
supported_color_modes: !lambda "return std::string(id(tile1_supported_color_modes).state.c_str());"
climate_current_temperature: !lambda "return id(tile1_climate_current_temperature).state;"
climate_target_temperature: !lambda "return id(tile1_climate_target_temperature).state;"
climate_humidity: !lambda "return id(tile1_climate_humidity).state;"
climate_mode: !lambda "return std::string(id(tile1_climate_mode).state.c_str());"
climate_supported_modes: !lambda "return std::string(id(tile1_climate_supported_modes).state.c_str());"
climate_min_temp: "${TILE1_CLIMATE_MIN_TEMP}"
climate_max_temp: "${TILE1_CLIMATE_MAX_TEMP}"
climate_step: "${TILE1_CLIMATE_STEP}"
# Long-press modus "vacuum": opent de stofzuigerbediening.
# Valt buiten de action/slider-keten, dus die blijft ongewijzigd.
- if:
condition:
lambda: "return std::string(\"${TILE1_LONGPRESS}\") == \"vacuum\";"
then:
- script.execute:
id: open_vacuum_overlay
title: "${TILE1_TITLE}"
entity: "${TILE1_ENTITY}"
# ---------- TILE 2 ----------
- obj:
id: tile2
x: ${GRID_COL2_X}
y: ${GRID_ROW1_Y}
width: ${TILE_W}
height: ${TILE_H}
styles: style_tile
scrollable: false
scrollbar_mode: 'OFF'
# Directe lokale feedback: LVGL past dit toe zodra de vinger het
# glas raakt, in dezelfde frame. Onafhankelijk van Home Assistant.
pressed:
bg_opa: 45%
widgets:
- obj:
id: tile2_icon_circle
x: ${TILE_ICON_X}
y: ${TILE_ICON_Y}
width: 54
height: 54
styles: style_icon_circle
clickable: false
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
id: tile2_icon_lbl
align: CENTER
text_font: materialdesign_icons
text_color: 0xFFFFFF
text: "${TILE2_ICON}"
clickable: false
- label:
id: t2_title
width: 130
long_mode: DOT
x: ${TILE_TITLE_X}
y: ${TILE_TITLE_Y}
text: "${TILE2_TITLE}"
styles: style_title
clickable: false
- label:
id: t2_value
width: 130
long_mode: DOT
x: ${TILE_VALUE_X}
y: ${TILE_VALUE_Y}
text: "—"
styles: style_value
clickable: false
on_short_click:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 2);'
then:
- script.execute: { id: publish_action, action: "tile2_press" }
- script.execute:
id: do_tile_action
tile_type: "${TILE2_TYPE}"
tap_action: "${TILE2_TAP_ACTION}"
service: "${TILE2_TAP_SERVICE}"
entity_id: "${TILE2_ENTITY}"
param_key: "${TILE2_TAP_PARAM_KEY}"
param_val: "${TILE2_TAP_PARAM_VAL}"
was_on: !lambda |-
if (std::string("${TILE2_TYPE}") == "cover")
return id(tile2_cover_state).state != "closed";
return std::string(id(ha_state_tile2).state.c_str()) == "on";
on_long_press:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 2);'
then:
- script.execute: { id: publish_action, action: "tile2_long_press" }
- if:
condition:
lambda: "return std::string(\"${TILE2_LONGPRESS}\") == \"action\";"
then:
- script.execute:
id: do_tile_action
tile_type: !lambda |-
std::string t = "${TILE2_LONGPRESS_ACTION_TYPE}";
return t.empty() ? std::string("${TILE2_TYPE}") : t;
tap_action: "activate"
service: "${TILE2_LONGPRESS_ACTION_SERVICE}"
entity_id: "${TILE2_LONGPRESS_ACTION}"
param_key: ""
param_val: ""
was_on: false
else:
- if:
condition:
lambda: "return std::string(\"${TILE2_LONGPRESS}\") == \"slider\";"
then:
- script.execute:
id: open_value_overlay
title: "${TILE2_TITLE}"
entity: "${TILE2_ENTITY}"
tile_type: "${TILE2_TYPE}"
longpress_mode: "${TILE2_LONGPRESS_SLIDER}"
was_on: !lambda |-
if (std::string("${TILE2_TYPE}") == "cover")
return id(tile2_cover_state).state != "closed";
return std::string(id(ha_state_tile2).state.c_str()) == "on";
off_value: !lambda "return atoi(\"${TILE2_LONGPRESS_OFF_VALUE}\");"
brightness_value: !lambda "return id(tile2_brightness).state;"
percentage_value: !lambda "return id(tile2_percentage).state;"
position_value: !lambda "return id(tile2_cover_position).state;"
icon_text: "${TILE2_ICON}"
slider_bg_color: ${TILE2_BG_DISABLED_COLOR}
slider_fill_color: ${TILE2_CIRCLE_ACTIVE_COLOR}
slider_icon_color: ${TILE2_ICON_ACTIVE_COLOR}
label_off: "${TILE2_LABEL_OFF}"
label_on: "${TILE2_LABEL_ON}"
supported_color_modes: !lambda "return std::string(id(tile2_supported_color_modes).state.c_str());"
climate_current_temperature: !lambda "return id(tile2_climate_current_temperature).state;"
climate_target_temperature: !lambda "return id(tile2_climate_target_temperature).state;"
climate_humidity: !lambda "return id(tile2_climate_humidity).state;"
climate_mode: !lambda "return std::string(id(tile2_climate_mode).state.c_str());"
climate_supported_modes: !lambda "return std::string(id(tile2_climate_supported_modes).state.c_str());"
climate_min_temp: "${TILE2_CLIMATE_MIN_TEMP}"
climate_max_temp: "${TILE2_CLIMATE_MAX_TEMP}"
climate_step: "${TILE2_CLIMATE_STEP}"
# Long-press modus "vacuum": opent de stofzuigerbediening.
# Valt buiten de action/slider-keten, dus die blijft ongewijzigd.
- if:
condition:
lambda: "return std::string(\"${TILE2_LONGPRESS}\") == \"vacuum\";"
then:
- script.execute:
id: open_vacuum_overlay
title: "${TILE2_TITLE}"
entity: "${TILE2_ENTITY}"
# ---------- TILE 3 ----------
- obj:
id: tile3
x: ${GRID_COL1_X}
y: ${GRID_ROW2_Y}
width: ${TILE_W}
height: ${TILE_H}
styles: style_tile
scrollable: false
scrollbar_mode: 'OFF'
# Directe lokale feedback: LVGL past dit toe zodra de vinger het
# glas raakt, in dezelfde frame. Onafhankelijk van Home Assistant.
pressed:
bg_opa: 45%
widgets:
- obj:
id: tile3_icon_circle
x: ${TILE_ICON_X}
y: ${TILE_ICON_Y}
width: 54
height: 54
styles: style_icon_circle
clickable: false
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
id: tile3_icon_lbl
align: CENTER
text_font: materialdesign_icons
text_color: 0xFFFFFF
text: "${TILE3_ICON}"
clickable: false
- label:
id: t3_title
width: 130
long_mode: DOT
x: ${TILE_TITLE_X}
y: ${TILE_TITLE_Y}
text: "${TILE3_TITLE}"
styles: style_title
clickable: false
- label:
id: t3_value
width: 130
long_mode: DOT
x: ${TILE_VALUE_X}
y: ${TILE_VALUE_Y}
text: "—"
styles: style_value
clickable: false
on_short_click:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 3);'
then:
- script.execute: { id: publish_action, action: "tile3_press" }
- script.execute:
id: do_tile_action
tile_type: "${TILE3_TYPE}"
tap_action: "${TILE3_TAP_ACTION}"
service: "${TILE3_TAP_SERVICE}"
entity_id: "${TILE3_ENTITY}"
param_key: "${TILE3_TAP_PARAM_KEY}"
param_val: "${TILE3_TAP_PARAM_VAL}"
was_on: !lambda |-
if (std::string("${TILE3_TYPE}") == "cover")
return id(tile3_cover_state).state != "closed";
return std::string(id(ha_state_tile3).state.c_str()) == "on";
on_long_press:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 3);'
then:
- script.execute: { id: publish_action, action: "tile3_long_press" }
- if:
condition:
lambda: "return std::string(\"${TILE3_LONGPRESS}\") == \"action\";"
then:
- script.execute:
id: do_tile_action
tile_type: !lambda |-
std::string t = "${TILE3_LONGPRESS_ACTION_TYPE}";
return t.empty() ? std::string("${TILE3_TYPE}") : t;
tap_action: "activate"
service: "${TILE3_LONGPRESS_ACTION_SERVICE}"
entity_id: "${TILE3_LONGPRESS_ACTION}"
param_key: ""
param_val: ""
was_on: false
else:
- if:
condition:
lambda: "return std::string(\"${TILE3_LONGPRESS}\") == \"slider\";"
then:
- script.execute:
id: open_value_overlay
title: "${TILE3_TITLE}"
entity: "${TILE3_ENTITY}"
tile_type: "${TILE3_TYPE}"
longpress_mode: "${TILE3_LONGPRESS_SLIDER}"
was_on: !lambda |-
if (std::string("${TILE3_TYPE}") == "cover")
return id(tile3_cover_state).state != "closed";
return std::string(id(ha_state_tile3).state.c_str()) == "on";
off_value: !lambda "return atoi(\"${TILE3_LONGPRESS_OFF_VALUE}\");"
brightness_value: !lambda "return id(tile3_brightness).state;"
percentage_value: !lambda "return id(tile3_percentage).state;"
position_value: !lambda "return id(tile3_cover_position).state;"
icon_text: "${TILE3_ICON}"
slider_bg_color: ${TILE3_BG_DISABLED_COLOR}
slider_fill_color: ${TILE3_CIRCLE_ACTIVE_COLOR}
slider_icon_color: ${TILE3_ICON_ACTIVE_COLOR}
label_off: "${TILE3_LABEL_OFF}"
label_on: "${TILE3_LABEL_ON}"
supported_color_modes: !lambda "return std::string(id(tile3_supported_color_modes).state.c_str());"
climate_current_temperature: !lambda "return id(tile3_climate_current_temperature).state;"
climate_target_temperature: !lambda "return id(tile3_climate_target_temperature).state;"
climate_humidity: !lambda "return id(tile3_climate_humidity).state;"
climate_mode: !lambda "return std::string(id(tile3_climate_mode).state.c_str());"
climate_supported_modes: !lambda "return std::string(id(tile3_climate_supported_modes).state.c_str());"
climate_min_temp: "${TILE3_CLIMATE_MIN_TEMP}"
climate_max_temp: "${TILE3_CLIMATE_MAX_TEMP}"
climate_step: "${TILE3_CLIMATE_STEP}"
# Long-press modus "vacuum": opent de stofzuigerbediening.
# Valt buiten de action/slider-keten, dus die blijft ongewijzigd.
- if:
condition:
lambda: "return std::string(\"${TILE3_LONGPRESS}\") == \"vacuum\";"
then:
- script.execute:
id: open_vacuum_overlay
title: "${TILE3_TITLE}"
entity: "${TILE3_ENTITY}"
# ---------- TILE 4 ----------
- obj:
id: tile4
x: ${GRID_COL2_X}
y: ${GRID_ROW2_Y}
width: ${TILE_W}
height: ${TILE_H}
styles: style_tile
scrollable: false
scrollbar_mode: 'OFF'
# Directe lokale feedback: LVGL past dit toe zodra de vinger het
# glas raakt, in dezelfde frame. Onafhankelijk van Home Assistant.
pressed:
bg_opa: 45%
widgets:
- obj:
id: tile4_icon_circle
x: ${TILE_ICON_X}
y: ${TILE_ICON_Y}
width: 54
height: 54
styles: style_icon_circle
clickable: false
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
id: tile4_icon_lbl
align: CENTER
text_font: materialdesign_icons
text_color: 0xFFFFFF
text: "${TILE4_ICON}"
clickable: false
- label:
id: t4_title
width: 130
long_mode: DOT
x: ${TILE_TITLE_X}
y: ${TILE_TITLE_Y}
text: "${TILE4_TITLE}"
styles: style_title
clickable: false
- label:
id: t4_value
width: 130
long_mode: DOT
x: ${TILE_VALUE_X}
y: ${TILE_VALUE_Y}
text: "—"
styles: style_value
clickable: false
on_short_click:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 4);'
then:
- script.execute: { id: publish_action, action: "tile4_press" }
- script.execute:
id: do_tile_action
tile_type: "${TILE4_TYPE}"
tap_action: "${TILE4_TAP_ACTION}"
service: "${TILE4_TAP_SERVICE}"
entity_id: "${TILE4_ENTITY}"
param_key: "${TILE4_TAP_PARAM_KEY}"
param_val: "${TILE4_TAP_PARAM_VAL}"
was_on: !lambda |-
if (std::string("${TILE4_TYPE}") == "cover")
return id(tile4_cover_state).state != "closed";
return std::string(id(ha_state_tile4).state.c_str()) == "on";
on_long_press:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 4);'
then:
- script.execute: { id: publish_action, action: "tile4_long_press" }
- if:
condition:
lambda: "return std::string(\"${TILE4_LONGPRESS}\") == \"action\";"
then:
- script.execute:
id: do_tile_action
tile_type: !lambda |-
std::string t = "${TILE4_LONGPRESS_ACTION_TYPE}";
return t.empty() ? std::string("${TILE4_TYPE}") : t;
tap_action: "activate"
service: "${TILE4_LONGPRESS_ACTION_SERVICE}"
entity_id: "${TILE4_LONGPRESS_ACTION}"
param_key: ""
param_val: ""
was_on: false
else:
- if:
condition:
lambda: "return std::string(\"${TILE4_LONGPRESS}\") == \"slider\";"
then:
- script.execute:
id: open_value_overlay
title: "${TILE4_TITLE}"
entity: "${TILE4_ENTITY}"
tile_type: "${TILE4_TYPE}"
longpress_mode: "${TILE4_LONGPRESS_SLIDER}"
was_on: !lambda |-
if (std::string("${TILE4_TYPE}") == "cover")
return id(tile4_cover_state).state != "closed";
return std::string(id(ha_state_tile4).state.c_str()) == "on";
off_value: !lambda "return atoi(\"${TILE4_LONGPRESS_OFF_VALUE}\");"
brightness_value: !lambda "return id(tile4_brightness).state;"
percentage_value: !lambda "return id(tile4_percentage).state;"
position_value: !lambda "return id(tile4_cover_position).state;"
icon_text: "${TILE4_ICON}"
slider_bg_color: ${TILE4_BG_DISABLED_COLOR}
slider_fill_color: ${TILE4_CIRCLE_ACTIVE_COLOR}
slider_icon_color: ${TILE4_ICON_ACTIVE_COLOR}
label_off: "${TILE4_LABEL_OFF}"
label_on: "${TILE4_LABEL_ON}"
supported_color_modes: !lambda "return std::string(id(tile4_supported_color_modes).state.c_str());"
climate_current_temperature: !lambda "return id(tile4_climate_current_temperature).state;"
climate_target_temperature: !lambda "return id(tile4_climate_target_temperature).state;"
climate_humidity: !lambda "return id(tile4_climate_humidity).state;"
climate_mode: !lambda "return std::string(id(tile4_climate_mode).state.c_str());"
climate_supported_modes: !lambda "return std::string(id(tile4_climate_supported_modes).state.c_str());"
climate_min_temp: "${TILE4_CLIMATE_MIN_TEMP}"
climate_max_temp: "${TILE4_CLIMATE_MAX_TEMP}"
climate_step: "${TILE4_CLIMATE_STEP}"
# Long-press modus "vacuum": opent de stofzuigerbediening.
# Valt buiten de action/slider-keten, dus die blijft ongewijzigd.
- if:
condition:
lambda: "return std::string(\"${TILE4_LONGPRESS}\") == \"vacuum\";"
then:
- script.execute:
id: open_vacuum_overlay
title: "${TILE4_TITLE}"
entity: "${TILE4_ENTITY}"
# ---------- TILE 5 ----------
- obj:
id: tile5
x: ${GRID_COL1_X}
y: ${GRID_ROW3_Y}
width: ${TILE_W}
height: ${TILE_H}
styles: style_tile
scrollable: false
scrollbar_mode: 'OFF'
# Directe lokale feedback: LVGL past dit toe zodra de vinger het
# glas raakt, in dezelfde frame. Onafhankelijk van Home Assistant.
pressed:
bg_opa: 45%
widgets:
- obj:
id: tile5_icon_circle
x: ${TILE_ICON_X}
y: ${TILE_ICON_Y}
width: 54
height: 54
styles: style_icon_circle
clickable: false
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
id: tile5_icon_lbl
align: CENTER
text_font: materialdesign_icons
text_color: 0xFFFFFF
text: "${TILE5_ICON}"
clickable: false
- label:
id: t5_title
width: 130
long_mode: DOT
x: ${TILE_TITLE_X}
y: ${TILE_TITLE_Y}
text: "${TILE5_TITLE}"
styles: style_title
clickable: false
- label:
id: t5_value
width: 130
long_mode: DOT
x: ${TILE_VALUE_X}
y: ${TILE_VALUE_Y}
text: "—"
styles: style_value
clickable: false
on_short_click:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 5);'
then:
- script.execute: { id: publish_action, action: "tile5_press" }
- script.execute:
id: do_tile_action
tile_type: "${TILE5_TYPE}"
tap_action: "${TILE5_TAP_ACTION}"
service: "${TILE5_TAP_SERVICE}"
entity_id: "${TILE5_ENTITY}"
param_key: "${TILE5_TAP_PARAM_KEY}"
param_val: "${TILE5_TAP_PARAM_VAL}"
was_on: !lambda |-
if (std::string("${TILE5_TYPE}") == "cover")
return id(tile5_cover_state).state != "closed";
return std::string(id(ha_state_tile5).state.c_str()) == "on";
on_long_press:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 5);'
then:
- script.execute: { id: publish_action, action: "tile5_long_press" }
- if:
condition:
lambda: "return std::string(\"${TILE5_LONGPRESS}\") == \"action\";"
then:
- script.execute:
id: do_tile_action
tile_type: !lambda |-
std::string t = "${TILE5_LONGPRESS_ACTION_TYPE}";
return t.empty() ? std::string("${TILE5_TYPE}") : t;
tap_action: "activate"
service: "${TILE5_LONGPRESS_ACTION_SERVICE}"
entity_id: "${TILE5_LONGPRESS_ACTION}"
param_key: ""
param_val: ""
was_on: false
else:
- if:
condition:
lambda: "return std::string(\"${TILE5_LONGPRESS}\") == \"slider\";"
then:
- script.execute:
id: open_value_overlay
title: "${TILE5_TITLE}"
entity: "${TILE5_ENTITY}"
tile_type: "${TILE5_TYPE}"
longpress_mode: "${TILE5_LONGPRESS_SLIDER}"
was_on: !lambda |-
if (std::string("${TILE5_TYPE}") == "cover")
return id(tile5_cover_state).state != "closed";
return std::string(id(ha_state_tile5).state.c_str()) == "on";
off_value: !lambda "return atoi(\"${TILE5_LONGPRESS_OFF_VALUE}\");"
brightness_value: !lambda "return id(tile5_brightness).state;"
percentage_value: !lambda "return id(tile5_percentage).state;"
position_value: !lambda "return id(tile5_cover_position).state;"
icon_text: "${TILE5_ICON}"
slider_bg_color: ${TILE5_BG_DISABLED_COLOR}
slider_fill_color: ${TILE5_CIRCLE_ACTIVE_COLOR}
slider_icon_color: ${TILE5_ICON_ACTIVE_COLOR}
label_off: "${TILE5_LABEL_OFF}"
label_on: "${TILE5_LABEL_ON}"
supported_color_modes: !lambda "return std::string(id(tile5_supported_color_modes).state.c_str());"
climate_current_temperature: !lambda "return id(tile5_climate_current_temperature).state;"
climate_target_temperature: !lambda "return id(tile5_climate_target_temperature).state;"
climate_humidity: !lambda "return id(tile5_climate_humidity).state;"
climate_mode: !lambda "return std::string(id(tile5_climate_mode).state.c_str());"
climate_supported_modes: !lambda "return std::string(id(tile5_climate_supported_modes).state.c_str());"
climate_min_temp: "${TILE5_CLIMATE_MIN_TEMP}"
climate_max_temp: "${TILE5_CLIMATE_MAX_TEMP}"
climate_step: "${TILE5_CLIMATE_STEP}"
# Long-press modus "vacuum": opent de stofzuigerbediening.
# Valt buiten de action/slider-keten, dus die blijft ongewijzigd.
- if:
condition:
lambda: "return std::string(\"${TILE5_LONGPRESS}\") == \"vacuum\";"
then:
- script.execute:
id: open_vacuum_overlay
title: "${TILE5_TITLE}"
entity: "${TILE5_ENTITY}"
# ---------- TILE 6 ----------
- obj:
id: tile6
x: ${GRID_COL2_X}
y: ${GRID_ROW3_Y}
width: ${TILE_W}
height: ${TILE_H}
styles: style_tile
scrollable: false
scrollbar_mode: 'OFF'
# Directe lokale feedback: LVGL past dit toe zodra de vinger het
# glas raakt, in dezelfde frame. Onafhankelijk van Home Assistant.
pressed:
bg_opa: 45%
widgets:
- obj:
id: tile6_icon_circle
x: ${TILE_ICON_X}
y: ${TILE_ICON_Y}
width: 54
height: 54
styles: style_icon_circle
clickable: false
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
id: tile6_icon_lbl
align: CENTER
text_font: materialdesign_icons
text_color: 0xFFFFFF
text: "${TILE6_ICON}"
clickable: false
- label:
id: t6_title
width: 130
long_mode: DOT
x: ${TILE_TITLE_X}
y: ${TILE_TITLE_Y}
text: "${TILE6_TITLE}"
styles: style_title
clickable: false
- label:
id: t6_value
width: 130
long_mode: DOT
x: ${TILE_VALUE_X}
y: ${TILE_VALUE_Y}
text: "—"
styles: style_value
clickable: false
on_short_click:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 6);'
then:
- script.execute: { id: publish_action, action: "tile6_press" }
- script.execute:
id: do_tile_action
tile_type: "${TILE6_TYPE}"
tap_action: "${TILE6_TAP_ACTION}"
service: "${TILE6_TAP_SERVICE}"
entity_id: "${TILE6_ENTITY}"
param_key: "${TILE6_TAP_PARAM_KEY}"
param_val: "${TILE6_TAP_PARAM_VAL}"
was_on: !lambda |-
if (std::string("${TILE6_TYPE}") == "cover")
return id(tile6_cover_state).state != "closed";
return std::string(id(ha_state_tile6).state.c_str()) == "on";
on_long_press:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 6);'
then:
- script.execute: { id: publish_action, action: "tile6_long_press" }
- if:
condition:
lambda: "return std::string(\"${TILE6_LONGPRESS}\") == \"action\";"
then:
- script.execute:
id: do_tile_action
tile_type: !lambda |-
std::string t = "${TILE6_LONGPRESS_ACTION_TYPE}";
return t.empty() ? std::string("${TILE6_TYPE}") : t;
tap_action: "activate"
service: "${TILE6_LONGPRESS_ACTION_SERVICE}"
entity_id: "${TILE6_LONGPRESS_ACTION}"
param_key: ""
param_val: ""
was_on: false
else:
- if:
condition:
lambda: "return std::string(\"${TILE6_LONGPRESS}\") == \"slider\";"
then:
- script.execute:
id: open_value_overlay
title: "${TILE6_TITLE}"
entity: "${TILE6_ENTITY}"
tile_type: "${TILE6_TYPE}"
longpress_mode: "${TILE6_LONGPRESS_SLIDER}"
was_on: !lambda |-
if (std::string("${TILE6_TYPE}") == "cover")
return id(tile6_cover_state).state != "closed";
return std::string(id(ha_state_tile6).state.c_str()) == "on";
off_value: !lambda "return atoi(\"${TILE6_LONGPRESS_OFF_VALUE}\");"
brightness_value: !lambda "return id(tile6_brightness).state;"
percentage_value: !lambda "return id(tile6_percentage).state;"
position_value: !lambda "return id(tile6_cover_position).state;"
icon_text: "${TILE6_ICON}"
slider_bg_color: ${TILE6_BG_DISABLED_COLOR}
slider_fill_color: ${TILE6_CIRCLE_ACTIVE_COLOR}
slider_icon_color: ${TILE6_ICON_ACTIVE_COLOR}
label_off: "${TILE6_LABEL_OFF}"
label_on: "${TILE6_LABEL_ON}"
supported_color_modes: !lambda "return std::string(id(tile6_supported_color_modes).state.c_str());"
climate_current_temperature: !lambda "return id(tile6_climate_current_temperature).state;"
climate_target_temperature: !lambda "return id(tile6_climate_target_temperature).state;"
climate_humidity: !lambda "return id(tile6_climate_humidity).state;"
climate_mode: !lambda "return std::string(id(tile6_climate_mode).state.c_str());"
climate_supported_modes: !lambda "return std::string(id(tile6_climate_supported_modes).state.c_str());"
climate_min_temp: "${TILE6_CLIMATE_MIN_TEMP}"
climate_max_temp: "${TILE6_CLIMATE_MAX_TEMP}"
climate_step: "${TILE6_CLIMATE_STEP}"
# Long-press modus "vacuum": opent de stofzuigerbediening.
# Valt buiten de action/slider-keten, dus die blijft ongewijzigd.
- if:
condition:
lambda: "return std::string(\"${TILE6_LONGPRESS}\") == \"vacuum\";"
then:
- script.execute:
id: open_vacuum_overlay
title: "${TILE6_TITLE}"
entity: "${TILE6_ENTITY}"
# ---------- TILE 7 — Tafellamp ----------
# Alleen-helderheid lamp: er zijn bewust geen percentage/cover/
# climate-sensors voor deze tegel aangemaakt, die zouden alleen
# maar HA-subscriptions en heap kosten. De overlay krijgt daarom
# NAN / "" voor de velden die een light nooit gebruikt.
- obj:
id: tile7
hidden: true
x: ${GRID_COL1_X}
y: ${GRID_ROW4_Y}
width: ${TILE_W}
height: ${TILE_H}
styles: style_tile
scrollable: false
scrollbar_mode: 'OFF'
pressed:
bg_opa: 45%
widgets:
- obj:
id: tile7_icon_circle
x: ${TILE_ICON_X}
y: ${TILE_ICON_Y}
width: 54
height: 54
styles: style_icon_circle
clickable: false
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
id: tile7_icon_lbl
align: CENTER
text_font: materialdesign_icons
text_color: 0xFFFFFF
text: "${TILE7_ICON}"
clickable: false
- label:
id: t7_title
width: 130
long_mode: DOT
x: ${TILE_TITLE_X}
y: ${TILE_TITLE_Y}
text: "${TILE7_TITLE}"
styles: style_title
clickable: false
- label:
id: t7_value
width: 130
long_mode: DOT
x: ${TILE_VALUE_X}
y: ${TILE_VALUE_Y}
text: "—"
styles: style_value
clickable: false
on_short_click:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 7);'
then:
- script.execute: { id: publish_action, action: "tile7_press" }
- script.execute:
id: do_tile_action
tile_type: "${TILE7_TYPE}"
tap_action: "${TILE7_TAP_ACTION}"
service: "${TILE7_TAP_SERVICE}"
entity_id: "${TILE7_ENTITY}"
param_key: "${TILE7_TAP_PARAM_KEY}"
param_val: "${TILE7_TAP_PARAM_VAL}"
was_on: !lambda "return std::string(id(ha_state_tile7).state.c_str()) == \"on\";"
on_long_press:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 7);'
then:
- script.execute: { id: publish_action, action: "tile7_long_press" }
- if:
condition:
lambda: "return std::string(\"${TILE7_LONGPRESS}\") == \"action\";"
then:
- script.execute:
id: do_tile_action
tile_type: !lambda |-
std::string t = "${TILE7_LONGPRESS_ACTION_TYPE}";
return t.empty() ? std::string("${TILE7_TYPE}") : t;
tap_action: "activate"
service: "${TILE7_LONGPRESS_ACTION_SERVICE}"
entity_id: "${TILE7_LONGPRESS_ACTION}"
param_key: ""
param_val: ""
was_on: false
else:
- if:
condition:
lambda: "return std::string(\"${TILE7_LONGPRESS}\") == \"slider\";"
then:
- script.execute:
id: open_value_overlay
title: "${TILE7_TITLE}"
entity: "${TILE7_ENTITY}"
tile_type: "${TILE7_TYPE}"
longpress_mode: "${TILE7_LONGPRESS_SLIDER}"
was_on: !lambda "return std::string(id(ha_state_tile7).state.c_str()) == \"on\";"
off_value: !lambda "return atoi(\"${TILE7_LONGPRESS_OFF_VALUE}\");"
brightness_value: !lambda "return id(tile7_brightness).state;"
percentage_value: !lambda "return NAN;"
position_value: !lambda "return NAN;"
icon_text: "${TILE7_ICON}"
slider_bg_color: ${TILE7_BG_DISABLED_COLOR}
slider_fill_color: ${TILE7_CIRCLE_ACTIVE_COLOR}
slider_icon_color: ${TILE7_ICON_ACTIVE_COLOR}
label_off: "${TILE7_LABEL_OFF}"
label_on: "${TILE7_LABEL_ON}"
supported_color_modes: !lambda "return std::string(id(tile7_supported_color_modes).state.c_str());"
climate_current_temperature: !lambda "return NAN;"
climate_target_temperature: !lambda "return NAN;"
climate_humidity: !lambda "return NAN;"
climate_mode: ""
climate_supported_modes: ""
climate_min_temp: "5.0"
climate_max_temp: "30.0"
climate_step: "0.5"
# ---------- TILE 8 — Brievenbus reset ----------
# input_select met opties Vol/Leeg. De tegel licht op zodra de
# status TILE8_ACTIVE_STATE ("Vol") is; een korte tik stuurt
# input_select.select_option met option: "Leeg".
# Geen slider-modus, dus deze tegel heeft alleen ha_state_tile8
# nodig — verder geen enkele HA-subscription.
- obj:
id: tile8
hidden: true
x: ${GRID_COL2_X}
y: ${GRID_ROW4_Y}
width: ${TILE_W}
height: ${TILE_H}
styles: style_tile
scrollable: false
scrollbar_mode: 'OFF'
pressed:
bg_opa: 45%
widgets:
- obj:
id: tile8_icon_circle
x: ${TILE_ICON_X}
y: ${TILE_ICON_Y}
width: 54
height: 54
styles: style_icon_circle
clickable: false
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
id: tile8_icon_lbl
align: CENTER
text_font: materialdesign_icons
text_color: 0xFFFFFF
text: "${TILE8_ICON}"
clickable: false
- label:
id: t8_title
width: 130
long_mode: DOT
x: ${TILE_TITLE_X}
y: ${TILE_TITLE_Y}
text: "${TILE8_TITLE}"
styles: style_title
clickable: false
- label:
id: t8_value
width: 130
long_mode: DOT
x: ${TILE_VALUE_X}
y: ${TILE_VALUE_Y}
text: "—"
styles: style_value
clickable: false
on_short_click:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 8);'
then:
- script.execute: { id: publish_action, action: "tile8_press" }
- script.execute:
id: do_tile_action
tile_type: "${TILE8_TYPE}"
tap_action: "${TILE8_TAP_ACTION}"
service: "${TILE8_TAP_SERVICE}"
entity_id: "${TILE8_ENTITY}"
param_key: "${TILE8_TAP_PARAM_KEY}"
param_val: "${TILE8_TAP_PARAM_VAL}"
was_on: !lambda "return std::string(id(ha_state_tile8).state.c_str()) == \"${TILE8_ACTIVE_STATE}\";"
on_long_press:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 8);'
then:
- script.execute: { id: publish_action, action: "tile8_long_press" }
# Slider-modus kan hier niet zonder eerst de bijbehorende
# sensors aan te maken; "action" werkt wel out of the box.
- if:
condition:
lambda: "return std::string(\"${TILE8_LONGPRESS}\") == \"action\";"
then:
- script.execute:
id: do_tile_action
tile_type: !lambda |-
std::string t = "${TILE8_LONGPRESS_ACTION_TYPE}";
return t.empty() ? std::string("${TILE8_TYPE}") : t;
tap_action: "activate"
service: "${TILE8_LONGPRESS_ACTION_SERVICE}"
entity_id: "${TILE8_LONGPRESS_ACTION}"
param_key: ""
param_val: ""
was_on: false
# ---------- TILE 9 — Woonkamer (verhuisd van tegel 2) ----------
- obj:
id: tile9
hidden: true
x: ${GRID_COL1_X}
y: ${GRID_ROW5_Y}
width: ${TILE_W}
height: ${TILE_H}
styles: style_tile
scrollable: false
scrollbar_mode: 'OFF'
pressed:
bg_opa: 45%
widgets:
- obj:
id: tile9_icon_circle
x: ${TILE_ICON_X}
y: ${TILE_ICON_Y}
width: 54
height: 54
styles: style_icon_circle
clickable: false
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
id: tile9_icon_lbl
align: CENTER
text_font: materialdesign_icons
text_color: 0xFFFFFF
text: "${TILE9_ICON}"
clickable: false
- label:
id: t9_title
width: 130
long_mode: DOT
x: ${TILE_TITLE_X}
y: ${TILE_TITLE_Y}
text: "${TILE9_TITLE}"
styles: style_title
clickable: false
- label:
id: t9_value
width: 130
long_mode: DOT
x: ${TILE_VALUE_X}
y: ${TILE_VALUE_Y}
text: "—"
styles: style_value
clickable: false
on_short_click:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 9);'
then:
- script.execute: { id: publish_action, action: "tile9_press" }
- script.execute:
id: do_tile_action
tile_type: "${TILE9_TYPE}"
tap_action: "${TILE9_TAP_ACTION}"
service: "${TILE9_TAP_SERVICE}"
entity_id: "${TILE9_ENTITY}"
param_key: "${TILE9_TAP_PARAM_KEY}"
param_val: "${TILE9_TAP_PARAM_VAL}"
was_on: !lambda "return std::string(id(ha_state_tile9).state.c_str()) == \"on\";"
on_long_press:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 9);'
then:
- script.execute: { id: publish_action, action: "tile9_long_press" }
- if:
condition:
lambda: "return std::string(\"${TILE9_LONGPRESS}\") == \"action\";"
then:
- script.execute:
id: do_tile_action
tile_type: !lambda |-
std::string t = "${TILE9_LONGPRESS_ACTION_TYPE}";
return t.empty() ? std::string("${TILE9_TYPE}") : t;
tap_action: "activate"
service: "${TILE9_LONGPRESS_ACTION_SERVICE}"
entity_id: "${TILE9_LONGPRESS_ACTION}"
param_key: ""
param_val: ""
was_on: false
else:
- if:
condition:
lambda: "return std::string(\"${TILE9_LONGPRESS}\") == \"slider\";"
then:
- script.execute:
id: open_value_overlay
title: "${TILE9_TITLE}"
entity: "${TILE9_ENTITY}"
tile_type: "${TILE9_TYPE}"
longpress_mode: "${TILE9_LONGPRESS_SLIDER}"
was_on: !lambda "return std::string(id(ha_state_tile9).state.c_str()) == \"on\";"
off_value: !lambda "return atoi(\"${TILE9_LONGPRESS_OFF_VALUE}\");"
brightness_value: !lambda "return id(tile9_brightness).state;"
percentage_value: !lambda "return NAN;"
position_value: !lambda "return NAN;"
icon_text: "${TILE9_ICON}"
slider_bg_color: ${TILE9_BG_DISABLED_COLOR}
slider_fill_color: ${TILE9_CIRCLE_ACTIVE_COLOR}
slider_icon_color: ${TILE9_ICON_ACTIVE_COLOR}
label_off: "${TILE9_LABEL_OFF}"
label_on: "${TILE9_LABEL_ON}"
supported_color_modes: !lambda "return std::string(id(tile9_supported_color_modes).state.c_str());"
climate_current_temperature: !lambda "return NAN;"
climate_target_temperature: !lambda "return NAN;"
climate_humidity: !lambda "return NAN;"
climate_mode: ""
climate_supported_modes: ""
climate_min_temp: "5.0"
climate_max_temp: "30.0"
climate_step: "0.5"
# ---------- TILE 10 — Alarm aan/uit ----------
- obj:
id: tile10
hidden: true
x: ${GRID_COL2_X}
y: ${GRID_ROW5_Y}
width: ${TILE_W}
height: ${TILE_H}
styles: style_tile
scrollable: false
scrollbar_mode: 'OFF'
pressed:
bg_opa: 45%
widgets:
- obj:
id: tile10_icon_circle
x: ${TILE_ICON_X}
y: ${TILE_ICON_Y}
width: 54
height: 54
styles: style_icon_circle
clickable: false
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
id: tile10_icon_lbl
align: CENTER
text_font: materialdesign_icons
text_color: 0xFFFFFF
text: "${TILE10_ICON}"
clickable: false
- label:
id: t10_title
width: 130
long_mode: DOT
x: ${TILE_TITLE_X}
y: ${TILE_TITLE_Y}
text: "${TILE10_TITLE}"
styles: style_title
clickable: false
- label:
id: t10_value
width: 130
long_mode: DOT
x: ${TILE_VALUE_X}
y: ${TILE_VALUE_Y}
text: "—"
styles: style_value
clickable: false
on_short_click:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 10);'
then:
- script.execute: { id: publish_action, action: "tile10_press" }
- script.execute:
id: do_tile_action
tile_type: "${TILE10_TYPE}"
tap_action: "${TILE10_TAP_ACTION}"
service: "${TILE10_TAP_SERVICE}"
entity_id: "${TILE10_ENTITY}"
param_key: "${TILE10_TAP_PARAM_KEY}"
param_val: "${TILE10_TAP_PARAM_VAL}"
was_on: !lambda "return std::string(id(ha_state_tile10).state.c_str()) == \"on\";"
on_long_press:
- if:
condition:
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 10);'
then:
- script.execute: { id: publish_action, action: "tile10_long_press" }
- if:
condition:
lambda: "return std::string(\"${TILE10_LONGPRESS}\") == \"action\";"
then:
- script.execute:
id: do_tile_action
tile_type: !lambda |-
std::string t = "${TILE10_LONGPRESS_ACTION_TYPE}";
return t.empty() ? std::string("${TILE10_TYPE}") : t;
tap_action: "activate"
service: "${TILE10_LONGPRESS_ACTION_SERVICE}"
entity_id: "${TILE10_LONGPRESS_ACTION}"
param_key: ""
param_val: ""
was_on: false
# ---------- VALUE SLIDER OVERLAY ----------
- obj:
id: page_prev
align: BOTTOM_LEFT
x: 14
y: -4
width: 150
height: 44
pad_all: 0
radius: 12
bg_color: 0x3C3C40
bg_opa: COVER
border_width: 0
scrollable: false
on_short_click:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 11);'
then:
- lambda: 'id(tile_page) = 0;'
- script.execute: show_tile_page
widgets:
- label:
align: CENTER
text: "< Vorige"
text_font: sublabel
text_color: 0xFFFFFF
clickable: false
- label:
id: page_number
align: BOTTOM_MID
y: -18
text: "1 / 2"
text_font: sublabel
text_color: 0xFFFFFF
- obj:
id: page_next
align: BOTTOM_RIGHT
x: -14
y: -4
width: 150
height: 44
pad_all: 0
radius: 12
bg_color: 0x3C3C40
bg_opa: COVER
border_width: 0
scrollable: false
on_short_click:
- if:
condition:
lambda: 'return cyd::touch_guard.accept(millis(), 12);'
then:
- lambda: 'id(tile_page) = 1;'
- script.execute: show_tile_page
widgets:
- label:
align: CENTER
text: "Volgende >"
text_font: sublabel
text_color: 0xFFFFFF
clickable: false
- obj:
id: brightness_overlay
x: 0
y: 0
width: ${DISPLAY_W}
height: ${DISPLAY_H}
bg_opa: TRANSP
hidden: true
scrollable: false
scrollbar_mode: 'OFF'
pad_left: 0
pad_right: 0
pad_top: 0
pad_bottom: 0
border_width: 0
widgets:
- obj:
id: brightness_overlay_bg
x: 0
y: 0
width: ${DISPLAY_W}
height: ${DISPLAY_H}
styles: style_bg_grad
scrollable: false
clickable: true
on_click:
- lvgl.widget.hide: brightness_overlay
- obj:
id: brightness_overlay_dim
x: 0
y: 0
width: ${DISPLAY_W}
height: ${DISPLAY_H}
bg_color: 0x000000
bg_opa: 18%
border_width: 0
clickable: false
scrollable: false
- label:
id: overlay_title
align: TOP_MID
y: 20
text: "Light"
styles: style_room
- label:
id: overlay_value
align: TOP_MID
y: 70
text: "100 %"
styles: style_value_big
- slider:
id: overlay_slider
align: CENTER
y: 36
width: ${OVERLAY_SLIDER_W}
height: ${OVERLAY_SLIDER_H}
min_value: 0
max_value: 100
value: 100
adv_hittest: false
bg_color: 0x293F50
bg_opa: 100%
radius: 42
# Enige widget met ZOWEL radius ALS een kind (overlay_icon).
# LVGL snijdt kinderen dan bij op de ronding, en dat kan
# alleen via een ARGB-laag van 96x160 = 46KB aaneengesloten.
# Lukt die allocatie niet, dan blijft LVGL het proberen tot
# de task watchdog het bord herstart. Ronding blijft, het
# bijsnijden gaat eraf.
clip_corner: false
border_width: 0
indicator:
bg_color: 0xFEC600
bg_opa: 100%
radius: 42
knob:
bg_opa: 0%
border_width: 0
shadow_width: 0
pad_all: 15
on_value:
- script.execute:
id: slider_preview
value: !lambda "return x;"
on_release:
- script.execute:
id: slider_commit
value: !lambda "return lv_slider_get_value(id(overlay_slider));"
# Het icoon zat als KIND in de slider. Een widget met een radius
# en kinderen moet die kinderen bijsnijden op de ronding, en dat
# kan LVGL alleen via een ARGB-laag van tientallen KB's. Zonder
# PSRAM mislukt dat soms, waarna LVGL blijft hertesten tot de
# watchdog het bord herstart. Als broer eroverheen is er geen
# laag nodig en ziet het er identiek uit.
- label:
id: overlay_icon
align: CENTER
y: ${OVERLAY_ICON_Y}
text_font: materialdesign_icons
text_color: 0xFFFFFF
text: "${TILE1_ICON}"
clickable: false
- obj:
id: overlay_color_button
align: BOTTOM_RIGHT
x: -20
y: -26
width: 66
height: 66
radius: 999
hidden: true
clickable: true
bg_opa: TRANSP
border_width: 0
pad_left: 0
pad_right: 0
pad_top: 0
pad_bottom: 0
scrollable: false
scrollbar_mode: 'OFF'
on_click:
- script.execute: open_color_detail
widgets:
- arc:
align: CENTER
width: 60
height: 60
start_angle: 0
end_angle: 45
min_value: 0
max_value: 100
value: 0
adjustable: false
arc_width: 15
arc_rounded: false
arc_color: 0xFF3B30
arc_opa: COVER
bg_opa: TRANSP
border_width: 0
clickable: false
indicator:
arc_opa: TRANSP
knob:
bg_opa: TRANSP
border_width: 0
- arc:
align: CENTER
width: 60
height: 60
start_angle: 45
end_angle: 90
min_value: 0
max_value: 100
value: 0
adjustable: false
arc_width: 15
arc_rounded: false
arc_color: 0xFF9500
arc_opa: COVER
bg_opa: TRANSP
border_width: 0
clickable: false
indicator:
arc_opa: TRANSP
knob:
bg_opa: TRANSP
border_width: 0
- arc:
align: CENTER
width: 60
height: 60
start_angle: 90
end_angle: 135
min_value: 0
max_value: 100
value: 0
adjustable: false
arc_width: 15
arc_rounded: false
arc_color: 0xFFD60A
arc_opa: COVER
bg_opa: TRANSP
border_width: 0
clickable: false
indicator:
arc_opa: TRANSP
knob:
bg_opa: TRANSP
border_width: 0
- arc:
align: CENTER
width: 60
height: 60
start_angle: 135
end_angle: 180
min_value: 0
max_value: 100
value: 0
adjustable: false
arc_width: 15
arc_rounded: false
arc_color: 0x30D158
arc_opa: COVER
bg_opa: TRANSP
border_width: 0
clickable: false
indicator:
arc_opa: TRANSP
knob:
bg_opa: TRANSP
border_width: 0
- arc:
align: CENTER
width: 60
height: 60
start_angle: 180
end_angle: 225
min_value: 0
max_value: 100
value: 0
adjustable: false
arc_width: 15
arc_rounded: false
arc_color: 0x32D6FF
arc_opa: COVER
bg_opa: TRANSP
border_width: 0
clickable: false
indicator:
arc_opa: TRANSP
knob:
bg_opa: TRANSP
border_width: 0
- arc:
align: CENTER
width: 60
height: 60
start_angle: 225
end_angle: 270
min_value: 0
max_value: 100
value: 0
adjustable: false
arc_width: 15
arc_rounded: false
arc_color: 0x0A84FF
arc_opa: COVER
bg_opa: TRANSP
border_width: 0
clickable: false
indicator:
arc_opa: TRANSP
knob:
bg_opa: TRANSP
border_width: 0
- arc:
align: CENTER
width: 60
height: 60
start_angle: 270
end_angle: 315
min_value: 0
max_value: 100
value: 0
adjustable: false
arc_width: 15
arc_rounded: false
arc_color: 0x5856D6
arc_opa: COVER
bg_opa: TRANSP
border_width: 0
clickable: false
indicator:
arc_opa: TRANSP
knob:
bg_opa: TRANSP
border_width: 0
- arc:
align: CENTER
width: 60
height: 60
start_angle: 315
end_angle: 360
min_value: 0
max_value: 100
value: 0
adjustable: false
arc_width: 15
arc_rounded: false
arc_color: 0xBF5AF2
arc_opa: COVER
bg_opa: TRANSP
border_width: 0
clickable: false
indicator:
arc_opa: TRANSP
knob:
bg_opa: TRANSP
border_width: 0
# ---------- LIGHT COLOR DETAIL OVERLAY ----------
- obj:
id: color_detail_overlay
x: 0
y: 0
width: ${DISPLAY_W}
height: ${DISPLAY_H}
bg_color: 0x171719
bg_opa: COVER
hidden: true
scrollable: false
scrollbar_mode: 'OFF'
pad_left: 0
pad_right: 0
pad_top: 0
pad_bottom: 0
border_width: 0
widgets:
- obj:
id: color_detail_bg
x: 0
y: 0
width: ${DISPLAY_W}
height: ${DISPLAY_H}
styles: style_bg_grad
scrollable: false
clickable: false
- obj:
id: color_detail_scrim
x: 0
y: 0
width: ${DISPLAY_W}
height: ${DISPLAY_H}
bg_color: 0x000000
bg_opa: 72%
border_width: 0
clickable: false
scrollable: false
- obj:
id: color_close_button
align: TOP_LEFT
x: 15
y: 18
width: 54
height: 54
radius: 999
bg_color: 0x3C3C40
bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 20%
clickable: true
scrollable: false
on_click:
- lvgl.widget.hide: color_detail_overlay
widgets:
- label:
align: CENTER
text: "x"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- label:
id: color_detail_title
width: 310
long_mode: DOT
text_align: CENTER
align: TOP_MID
y: 32
text: "Light Color"
styles: style_room
- obj:
id: color_done_button
align: TOP_RIGHT
x: -15
y: 18
width: 54
height: 54
radius: 999
bg_color: 0xFF9F0A
bg_opa: COVER
border_width: 0
clickable: true
scrollable: false
on_click:
- lvgl.widget.hide: color_detail_overlay
widgets:
- label:
align: CENTER
text: "\U000F012C"
text_font: materialdesign_icons
text_color: 0xFFFFFF
clickable: false
# ---------- CLIMATE DETAIL OVERLAY ----------
- obj:
id: climate_detail_overlay
x: 0
y: 0
width: ${DISPLAY_W}
height: ${DISPLAY_H}
bg_color: 0x171719
bg_opa: COVER
hidden: true
scrollable: false
scrollbar_mode: 'OFF'
pad_left: 0
pad_right: 0
pad_top: 0
pad_bottom: 0
border_width: 0
widgets:
- obj:
id: climate_detail_bg
x: 0
y: 0
width: ${DISPLAY_W}
height: ${DISPLAY_H}
styles: style_bg_grad
scrollable: false
clickable: false
- obj:
id: climate_detail_scrim
x: 0
y: 0
width: ${DISPLAY_W}
height: ${DISPLAY_H}
bg_color: 0x000000
bg_opa: 62%
border_width: 0
clickable: false
scrollable: false
- obj:
id: climate_close_button
align: TOP_LEFT
x: 15
y: 18
# 44 i.p.v. 36: iets groter doelvlak, groeit weg van de arc
# (top-left anchor blijft staan) i.p.v. ernaartoe.
width: 66
height: 66
radius: 999
bg_color: 0x3C3C40
bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 20%
clickable: true
scrollable: false
on_click:
- lvgl.widget.hide: climate_detail_overlay
widgets:
- label:
align: CENTER
text: "x"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: climate_done_button
align: TOP_RIGHT
x: -15
y: 18
width: 66
height: 66
radius: 999
bg_color: 0xFEC600
bg_opa: COVER
border_width: 0
clickable: true
scrollable: false
on_click:
- lvgl.widget.hide: climate_detail_overlay
widgets:
- label:
align: CENTER
text: "\U000F012C"
text_font: materialdesign_icons
text_color: 0xFFFFFF
clickable: false
- label:
id: climate_detail_title
align: TOP_MID
y: 32
text: "Climate"
styles: style_room
- label:
id: climate_current_label
align: TOP_MID
y: 84
text: "Current: --"
text_font: sublabel_big
text_color: 0xD1E8FF
text_opa: 85%
clickable: false
- arc:
id: climate_arc
align: CENTER
y: 40
width: 276
height: 276
# Zonder dit claimt de arc een onzichtbare rand extra
# klikgebied buiten de 184x184 box — vlakbij de kruisje-
# knop linksboven ontstaat daardoor overlap.
ext_click_area: 0
start_angle: 135
end_angle: 45
min_value: 50
max_value: 300
value: 200
adjustable: true
arc_width: 30
arc_rounded: true
arc_color: 0x4B4B4F
arc_opa: 80%
bg_opa: TRANSP
border_width: 0
indicator:
arc_color: 0xFEC600
arc_opa: COVER
arc_width: 30
arc_rounded: true
knob:
bg_color: 0xFFFFFF
bg_opa: COVER
border_width: 0
shadow_width: 0
on_value:
- script.execute:
id: climate_target_preview
value: !lambda "return x;"
on_release:
- script.execute:
id: climate_target_commit
value: !lambda "return lv_arc_get_value(id(climate_arc));"
- label:
id: climate_set_label
align: CENTER
y: -32
text: "SET TO"
text_font: sublabel_big
text_color: 0xFFFFFF
text_opa: 68%
clickable: false
- label:
id: climate_target_label
align: CENTER
y: 40
text: "20.0°"
text_font: climate_number
text_color: 0xFFFFFF
clickable: false
- obj:
id: climate_minus_button
align: BOTTOM_LEFT
x: 57
y: -32
width: 72
height: 72
radius: 22
bg_color: 0x3C3C40
bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 16%
clickable: true
scrollable: false
on_click:
- script.execute:
id: climate_adjust_target
direction: -1
widgets:
- label:
align: CENTER
text: "-"
text_font: headline
text_color: 0xFFFFFF
clickable: false
- obj:
id: climate_plus_button
align: BOTTOM_RIGHT
x: -57
y: -32
width: 72
height: 72
radius: 22
bg_color: 0x3C3C40
bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 16%
clickable: true
scrollable: false
on_click:
- script.execute:
id: climate_adjust_target
direction: 1
widgets:
- label:
align: CENTER
text: "+"
text_font: headline
text_color: 0xFFFFFF
clickable: false
- obj:
id: climate_mode_pill
align: BOTTOM_MID
y: -36
width: 189
height: 68
radius: 26
bg_color: 0x2C2C30
bg_opa: 82%
border_width: 0
clickable: true
scrollable: false
on_click:
- script.execute: open_climate_mode_picker
widgets:
- label:
id: climate_mode_label
align: CENTER
text: "Auto"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
# ---------- CLIMATE MODE PICKER ----------
# Vervangt het doorcyclen van modi. Cyclen stuurt bij elke tik een
# set_hvac_mode, dus om van 'cool' naar 'dry' te komen zet je een
# warmtepomp onderweg ook echt in 'heat' en 'fan_only'. Hier kies je
# rechtstreeks, en wordt er precies een opdracht verstuurd.
- obj:
id: climate_mode_overlay
x: 0
y: 0
width: ${DISPLAY_W}
height: ${DISPLAY_H}
hidden: true
bg_color: 0x000000
bg_opa: 90%
border_width: 0
clickable: true
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
align: TOP_MID
y: 32
text: "Mode"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
# Flex zorgt dat niet-ondersteunde modi geen gat achterlaten:
# LVGL slaat verborgen kinderen over bij het schikken.
- obj:
id: climate_mode_list
align: TOP_MID
y: 100
width: 98%
height: 356
bg_opa: TRANSP
border_width: 0
scrollable: false
pad_all: 4
layout:
type: FLEX
flex_flow: ROW_WRAP
flex_align_main: CENTER
flex_align_cross: CENTER
flex_align_track: CENTER
widgets:
- obj:
id: cmode_off
width: 198
height: 76
radius: 28
bg_color: 0x2C2C30
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: set_climate_mode, mode: "off" }
widgets:
- label:
align: CENTER
text: "Off"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: cmode_heat
width: 198
height: 76
radius: 28
bg_color: 0x2C2C30
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: set_climate_mode, mode: "heat" }
widgets:
- label:
align: CENTER
text: "Heat"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: cmode_cool
width: 198
height: 76
radius: 28
bg_color: 0x2C2C30
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: set_climate_mode, mode: "cool" }
widgets:
- label:
align: CENTER
text: "Cool"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: cmode_heat_cool
width: 198
height: 76
radius: 28
bg_color: 0x2C2C30
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: set_climate_mode, mode: "heat_cool" }
widgets:
- label:
align: CENTER
text: "Heat/Cool"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: cmode_auto
width: 198
height: 76
radius: 28
bg_color: 0x2C2C30
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: set_climate_mode, mode: "auto" }
widgets:
- label:
align: CENTER
text: "Auto"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: cmode_dry
width: 198
height: 76
radius: 28
bg_color: 0x2C2C30
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: set_climate_mode, mode: "dry" }
widgets:
- label:
align: CENTER
text: "Dry"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: cmode_fan_only
width: 198
height: 76
radius: 28
bg_color: 0x2C2C30
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: set_climate_mode, mode: "fan_only" }
widgets:
- label:
align: CENTER
text: "Fan"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: climate_mode_overlay_x
align: TOP_LEFT
x: 15
y: 18
width: 54
height: 54
radius: 999
bg_color: 0x3C3C40
bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 20%
clickable: true
scrollable: false
on_click:
- lvgl.widget.hide: climate_mode_overlay
widgets:
- label:
align: CENTER
text: "x"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: climate_mode_overlay_ok
align: TOP_RIGHT
x: -15
y: 18
width: 54
height: 54
radius: 999
bg_color: 0xFEC600
bg_opa: COVER
border_width: 0
clickable: true
scrollable: false
on_click:
- lvgl.widget.hide: climate_mode_overlay
widgets:
- label:
align: CENTER
text: "\U000F012C"
text_font: materialdesign_icons
text_color: 0xFFFFFF
clickable: false
# ---------- VACUUM CONTROL ----------
# Een stofzuiger is geen schakelaar: start, pauze, stop en terug naar
# het dock zijn losse opdrachten, en de zuigkracht is een aparte as.
- obj:
id: vacuum_overlay
x: 0
y: 0
width: ${DISPLAY_W}
height: ${DISPLAY_H}
hidden: true
bg_color: 0x000000
bg_opa: 90%
border_width: 0
clickable: true
scrollable: false
scrollbar_mode: 'OFF'
widgets:
- label:
id: vacuum_title
align: TOP_MID
y: 32
text: "Vacuum"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: vacuum_actions
align: TOP_MID
y: 104
width: 98%
height: 160
bg_opa: TRANSP
border_width: 0
scrollable: false
pad_all: 4
layout:
type: FLEX
flex_flow: ROW_WRAP
flex_align_main: CENTER
flex_align_cross: CENTER
flex_align_track: CENTER
widgets:
- obj:
id: vac_start
width: 108
height: 76
radius: 28
bg_color: 0x2C2C30
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: vacuum_action, service: "vacuum.start" }
widgets:
- label:
align: CENTER
text: "Start"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: vac_pause
width: 108
height: 76
radius: 28
bg_color: 0x2C2C30
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: vacuum_action, service: "vacuum.pause" }
widgets:
- label:
align: CENTER
text: "Pause"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: vac_return_to_base
width: 108
height: 76
radius: 28
bg_color: 0x2C2C30
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: vacuum_action, service: "vacuum.return_to_base" }
widgets:
- label:
align: CENTER
text: "Dock"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: vac_stop
width: 108
height: 76
radius: 28
bg_color: 0x2C2C30
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute: { id: vacuum_action, service: "vacuum.stop" }
widgets:
- label:
align: CENTER
text: "Stop"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- label:
align: TOP_MID
y: 276
text: "Suction"
text_font: sublabel
text_color: 0xFFFFFF
clickable: false
- obj:
id: vacuum_speeds
align: TOP_MID
y: 312
width: 98%
height: 148
bg_opa: TRANSP
border_width: 0
scrollable: false
pad_all: 4
layout:
type: FLEX
flex_flow: ROW_WRAP
flex_align_main: CENTER
flex_align_cross: CENTER
flex_align_track: CENTER
widgets:
- obj:
id: vacspd_quiet
width: 108
height: 44
radius: 28
bg_color: 0x1F1F23
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute:
id: vacuum_fan_speed
speed: !lambda 'return runtime_tiles::enabled ? runtime_tiles::vacuum_option(0) : "quiet";'
widgets:
- label:
align: CENTER
text: "Quiet"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: vacspd_balanced
width: 108
height: 44
radius: 28
bg_color: 0x1F1F23
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute:
id: vacuum_fan_speed
speed: !lambda 'return runtime_tiles::enabled ? runtime_tiles::vacuum_option(1) : "balanced";'
widgets:
- label:
align: CENTER
text: "Balanced"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: vacspd_turbo
width: 108
height: 44
radius: 28
bg_color: 0x1F1F23
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute:
id: vacuum_fan_speed
speed: !lambda 'return runtime_tiles::enabled ? runtime_tiles::vacuum_option(2) : "turbo";'
widgets:
- label:
align: CENTER
text: "Turbo"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: vacspd_max
width: 108
height: 44
radius: 28
bg_color: 0x1F1F23
bg_opa: 92%
border_width: 0
clickable: true
scrollable: false
pad_all: 0
on_click:
- script.execute:
id: vacuum_fan_speed
speed: !lambda 'return runtime_tiles::enabled ? runtime_tiles::vacuum_option(3) : "max";'
widgets:
- label:
align: CENTER
text: "Max"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: vacuum_overlay_x
align: TOP_LEFT
x: 15
y: 18
width: 54
height: 54
radius: 999
bg_color: 0x3C3C40
bg_opa: COVER
border_width: 1
border_color: 0xFFFFFF
border_opa: 20%
clickable: true
scrollable: false
on_click:
- lvgl.widget.hide: vacuum_overlay
widgets:
- label:
align: CENTER
text: "x"
text_font: sublabel_big
text_color: 0xFFFFFF
clickable: false
- obj:
id: vacuum_overlay_ok
align: TOP_RIGHT
x: -15
y: 18
width: 54
height: 54
radius: 999
bg_color: 0xFEC600
bg_opa: COVER
border_width: 0
clickable: true
scrollable: false
on_click:
- lvgl.widget.hide: vacuum_overlay
widgets:
- label:
align: CENTER
text: "\U000F012C"
text_font: materialdesign_icons
text_color: 0xFFFFFF
clickable: false
# ---------- DIM / WAKE OVERLAY ----------
- obj:
id: dim_wake_overlay
x: 0
y: 0
width: ${DISPLAY_W}
height: ${DISPLAY_H}
hidden: true
bg_opa: TRANSP
border_width: 0
scrollable: false
scrollbar_mode: 'OFF'
clickable: true
on_click:
- script.execute: wake_display
- id: touch_cal_page
bg_color: 0x111D2B
bg_opa: COVER
pad_all: 0
border_width: 0
scrollable: false
widgets:
- label:
align: TOP_MID
y: 110
text: "GT911 touchcontrole"
text_font: headline
text_color: 0xFFFFFF
- obj:
x: 12
y: 12
width: 24
height: 24
pad_all: 0
border_width: 0
bg_opa: TRANSP
scrollable: false
widgets:
- label:
align: CENTER
text: "+"
text_font: headline
text_color: 0xFFFFFF
- obj:
x: 443
y: 12
width: 24
height: 24
pad_all: 0
border_width: 0
bg_opa: TRANSP
scrollable: false
widgets:
- label:
align: CENTER
text: "+"
text_font: headline
text_color: 0xFFFFFF
- obj:
x: 443
y: 443
width: 24
height: 24
pad_all: 0
border_width: 0
bg_opa: TRANSP
scrollable: false
widgets:
- label:
align: CENTER
text: "+"
text_font: headline
text_color: 0xFFFFFF
- obj:
x: 12
y: 443
width: 24
height: 24
pad_all: 0
border_width: 0
bg_opa: TRANSP
scrollable: false
widgets:
- label:
align: CENTER
text: "+"
text_font: headline
text_color: 0xFFFFFF
- obj:
x: 228
y: 228
width: 24
height: 24
pad_all: 0
border_width: 0
bg_opa: TRANSP
scrollable: false
widgets:
- label:
align: CENTER
text: "+"
text_font: headline
text_color: 0xFFFFFF
script:
- id: capture_ui_snapshot
mode: single
then:
- lambda: guition::snapshot_begin();
- repeat:
count: 240
then:
- lambda: guition::snapshot_next_row();
- delay: 100ms
# Render every overlay and both pages repeatedly without controlling devices.
- id: ui_self_test
mode: single
then:
- logger.log: "UI_TEST START: page/overlay render stress, no HA actions"
- lambda: 'ESP_LOGI("ui_test", "Light sliders: %s", light_controls::self_test() ? "PASS" : "FAIL");'
- lambda: 'id(calibration_active) = false;'
- lvgl.page.show: home_page
- script.execute: wake_display
- repeat:
count: 10
then:
- lambda: |-
id(last_touch_ms) = millis();
id(tile_page) = 1;
- script.execute: show_tile_page
- delay: 150ms
- lambda: |-
lv_obj_update_layout(id(tile_scroll));
const int count = runtime_tiles::enabled ? runtime_tiles::model.count : std::clamp(atoi("${TILE_COUNT}"), 1, 10);
const bool paginated = count > 6;
lv_obj_t* tiles[] = {id(tile1), id(tile2), id(tile3), id(tile4), id(tile5),
id(tile6), id(tile7), id(tile8), id(tile9), id(tile10)};
bool ok = guition::check_geometry(id(home_page)->obj);
for (int i = 0; i < 10; ++i) {
bool visible = i < count && (paginated ? i >= 6 : i < 6);
ok = ok && (lv_obj_has_flag(tiles[i], LV_OBJ_FLAG_HIDDEN) != visible);
}
for (lv_obj_t* control : {id(page_prev), id(page_next), id(page_number)})
ok = ok && (lv_obj_has_flag(control, LV_OBJ_FLAG_HIDDEN) != paginated);
lv_area_t nav, grid;
lv_obj_get_coords(id(page_next), &nav);
lv_obj_get_coords(id(tile_scroll), &grid);
ok = ok && (!paginated || grid.y2 < nav.y1);
ESP_LOGI("ui_test", "page_check=%s count=%d heap=%u frames=%u grid_bottom=%d nav_top=%d", ok ? "PASS" : "FAIL", count, (unsigned) esp_get_free_heap_size(), (unsigned) id(rendered_frames), grid.y2, nav.y1);
id(tile_page) = 0;
- script.execute: show_tile_page
- delay: 150ms
- lvgl.widget.show: brightness_overlay
- delay: 150ms
- lambda: guition::check_geometry(id(brightness_overlay));
- lvgl.widget.hide: brightness_overlay
- lvgl.widget.show: color_detail_overlay
- delay: 150ms
- lambda: guition::check_geometry(id(color_detail_overlay));
- lvgl.widget.hide: color_detail_overlay
- lvgl.widget.show: climate_detail_overlay
- delay: 150ms
- lambda: guition::check_geometry(id(climate_detail_overlay));
- lvgl.widget.hide: climate_detail_overlay
- lvgl.widget.show: climate_mode_overlay
- delay: 150ms
- lambda: guition::check_geometry(id(climate_mode_overlay));
- lvgl.widget.hide: climate_mode_overlay
- lvgl.widget.show: vacuum_overlay
- delay: 150ms
- lambda: guition::check_geometry(id(vacuum_overlay));
- lvgl.widget.hide: vacuum_overlay
- lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();'
- logger.log: "UI_TEST COMPLETE"
- id: show_tile_page
then:
- lambda: |-
lv_obj_t* tiles[] = {id(tile1), id(tile2), id(tile3), id(tile4), id(tile5),
id(tile6), id(tile7), id(tile8), id(tile9), id(tile10)};
const int rows[] = {${GRID_ROW1_Y}, ${GRID_ROW2_Y}, ${GRID_ROW3_Y}};
const int count = runtime_tiles::enabled ? runtime_tiles::model.count : std::clamp(atoi("${TILE_COUNT}"), 1, 10);
const bool paginated = count > 6;
if (!paginated) id(tile_page) = 0;
for (lv_obj_t* control : {id(page_prev), id(page_next), id(page_number)}) {
if (paginated) lv_obj_clear_flag(control, LV_OBJ_FLAG_HIDDEN);
else lv_obj_add_flag(control, LV_OBJ_FLAG_HIDDEN);
}
for (int i = 0; i < 10; ++i) {
bool visible = i < count && (id(tile_page) == 0 ? i < 6 : i >= 6);
if (visible) {
lv_obj_set_y(tiles[i], rows[(i < 6 ? i : i - 6) / 2]);
lv_obj_clear_flag(tiles[i], LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(tiles[i], LV_OBJ_FLAG_HIDDEN);
}
}
lv_label_set_text(id(page_number), id(tile_page) == 0 ? "1 / 2" : "2 / 2");
if (id(tile_page) == 0) {
lv_obj_add_state(id(page_prev), LV_STATE_DISABLED);
lv_obj_clear_state(id(page_next), LV_STATE_DISABLED);
} else {
lv_obj_clear_state(id(page_prev), LV_STATE_DISABLED);
lv_obj_add_state(id(page_next), LV_STATE_DISABLED);
}
- id: wake_display
mode: restart
then:
- lambda: |-
id(last_touch_ms) = millis();
id(display_dimmed) = false;
- light.turn_on:
id: back_light
# Bijna instant omhoog. De ESPHome-standaard is 1s, en dat voelt traag
# als je het scherm aanraakt en op licht staat te wachten.
transition_length: 80ms
brightness: !lambda "return atoi(\"${MAX_BRIGHTNESS}\") / 100.0f;"
- lvgl.widget.hide: dim_wake_overlay
- lvgl.widget.hide: color_detail_overlay
- lvgl.widget.hide: climate_detail_overlay
- lvgl.widget.hide: climate_mode_overlay
- lvgl.widget.hide: vacuum_overlay
- id: dim_display
mode: restart
then:
- if:
condition:
lambda: 'return std::string("${AUTO_DIM}") == "true";'
then:
- lambda: |-
int dim_value = atoi("${DIM_BRIGHTNESS}");
int max_value = atoi("${MAX_BRIGHTNESS}");
if (std::string("${NIGHT_MODE}") == "true") {
auto now = id(homeassistant_time).now();
// Vóór de eerste tijd-sync (zie time: hierboven) is now()
// epoch 0 — dan geen nachtmodus-beslissing nemen op basis
// van een ongeldig uur, gewoon normale DIM_BRIGHTNESS.
if (now.is_valid()) {
int hour = now.hour;
int start_hour = atoi("${NIGHT_START_HOUR}");
int end_hour = atoi("${NIGHT_END_HOUR}");
bool is_night = false;
if (start_hour < end_hour) {
is_night = (hour >= start_hour && hour < end_hour);
} else {
is_night = (hour >= start_hour || hour < end_hour);
}
if (is_night) {
dim_value = atoi("${NIGHT_DIM_BRIGHTNESS}");
}
}
}
// Rustig omlaag faden is prettig; omhoog moet juist snel.
id(back_light).turn_on().set_transition_length(900).set_brightness(dim_value / 100.0f).perform();
// Always mark as dimmed to stop the interval from re-firing
id(display_dimmed) = true;
// Only show the touch-blocking overlay when brightness actually changes
if (dim_value < max_value) {
lv_obj_clear_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN);
}
# Terug naar het basisscherm bij standby. Bewust via de ESPHome
# acties en NIET met lv_obj_add_flag in een lambda: dit script
# draait vanuit een interval, dus buiten LVGL's eigen afhandeling.
- lvgl.widget.hide: brightness_overlay
- lvgl.widget.hide: color_detail_overlay
- lvgl.widget.hide: climate_detail_overlay
- lvgl.widget.hide: climate_mode_overlay
- lvgl.widget.hide: vacuum_overlay
- id: publish_action
mode: restart
parameters:
action: string
then:
- logger.log:
level: INFO
format: "publish_action: %s"
args: ["action.c_str()"]
- text_sensor.template.publish:
id: smartdisplay_action
state: !lambda 'return action;'
- delay: 500ms
- text_sensor.template.publish:
id: smartdisplay_action
state: ""
- id: climate_target_preview
parameters:
value: int
then:
- lambda: |-
id(climate_edit_ms) = millis();
id(climate_edit_pending) = true;
int min_x10 = id(active_climate_min_x10);
int max_x10 = id(active_climate_max_x10);
value = cyd::quantize_temperature(value, min_x10, max_x10,
id(active_climate_step_x10));
id(active_climate_target_x10) = value;
char buf[16];
snprintf(buf, sizeof(buf), "%.1f°", value / 10.0f);
lv_label_set_text(id(climate_target_label), buf);
lv_arc_set_value(id(climate_arc), value);
- id: climate_target_commit
parameters:
value: int
then:
- script.execute:
id: climate_target_preview
value: !lambda "return value;"
- if:
condition:
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true";'
then:
- homeassistant.action:
action: climate.set_temperature
data:
entity_id: !lambda "return id(active_entity);"
temperature: !lambda "return id(active_climate_target_x10) / 10.0f;"
- id: climate_adjust_target
parameters:
direction: int
then:
- lambda: |-
int step_x10 = id(active_climate_step_x10);
if (step_x10 < 5) step_x10 = 5;
int next = id(active_climate_target_x10) + (direction * step_x10);
if (next < id(active_climate_min_x10)) next = id(active_climate_min_x10);
if (next > id(active_climate_max_x10)) next = id(active_climate_max_x10);
id(active_climate_target_x10) = next;
- script.execute:
id: climate_target_commit
value: !lambda "return id(active_climate_target_x10);"
# Opent de moduskiezer. Verbergt modi die de entiteit niet ondersteunt en
# markeert de huidige, zodat je ziet waar je staat voor je iets kiest.
# Opent de stofzuigerbediening en onthoudt om welke entiteit het gaat.
- id: open_vacuum_overlay
mode: restart
parameters:
title: string
entity: string
then:
- lambda: |-
id(active_entity) = entity;
lv_label_set_text(id(vacuum_title), title.c_str());
- lvgl.widget.show: vacuum_overlay
# Een losse opdracht: start, pause, stop of return_to_base.
- id: vacuum_action
mode: queued
parameters:
service: string
then:
- if:
condition:
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true";'
then:
- homeassistant.action:
action: !lambda "return service;"
data:
entity_id: !lambda "return id(active_entity);"
# Zuigkracht is een aparte as, los van start/stop.
- id: vacuum_fan_speed
mode: queued
parameters:
speed: string
then:
- if:
condition:
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true";'
then:
- homeassistant.action:
action: vacuum.set_fan_speed
data:
entity_id: !lambda "return id(active_entity);"
fan_speed: !lambda "return speed;"
- id: open_climate_mode_picker
mode: restart
then:
- lambda: |-
auto lower = [](std::string v) {
for (char &c : v) { if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); }
return v;
};
std::string supported = lower(id(active_climate_supported_modes));
std::string current = lower(id(active_climate_mode));
auto has = [&](const char* m) {
std::string mode(m);
if (supported.empty() || supported == "unknown" || supported == "unavailable") return false;
return supported.find("'" + mode + "'") != std::string::npos ||
supported.find("\"" + mode + "\"") != std::string::npos;
};
auto apply = [&](lv_obj_t* btn, const char* m) {
if (has(m)) lv_obj_clear_flag(btn, LV_OBJ_FLAG_HIDDEN);
else lv_obj_add_flag(btn, LV_OBJ_FLAG_HIDDEN);
bool selected = (current == std::string(m));
lv_obj_set_style_bg_color(btn,
lv_color_hex(selected ? 0xFF9F0A : 0x2C2C30), LV_PART_MAIN);
};
apply(id(cmode_off), "off");
apply(id(cmode_heat), "heat");
apply(id(cmode_cool), "cool");
apply(id(cmode_heat_cool), "heat_cool");
apply(id(cmode_auto), "auto");
apply(id(cmode_dry), "dry");
apply(id(cmode_fan_only), "fan_only");
- lvgl.widget.show: climate_mode_overlay
# Zet precies een modus. Geen tussenstappen, dus geen ongewenste
# commando's naar de warmtepomp onderweg.
- id: set_climate_mode
mode: restart
parameters:
mode: string
then:
- lambda: |-
id(climate_edit_ms) = millis();
id(climate_edit_pending) = true;
id(active_climate_mode) = mode;
if (mode == "off") lv_label_set_text(id(climate_mode_label), "Off");
if (mode == "heat") lv_label_set_text(id(climate_mode_label), "Heat");
if (mode == "cool") lv_label_set_text(id(climate_mode_label), "Cool");
if (mode == "heat_cool") lv_label_set_text(id(climate_mode_label), "Heat/Cool");
if (mode == "auto") lv_label_set_text(id(climate_mode_label), "Auto");
if (mode == "dry") lv_label_set_text(id(climate_mode_label), "Dry");
if (mode == "fan_only") lv_label_set_text(id(climate_mode_label), "Fan");
- lvgl.widget.hide: climate_mode_overlay
- if:
condition:
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true";'
then:
- homeassistant.action:
action: climate.set_hvac_mode
data:
entity_id: !lambda "return id(active_entity);"
hvac_mode: !lambda "return id(active_climate_mode);"
# Isolated 40-second preview; every light commit is suppressed in the helper.
- id: light_controls_preview
mode: single
then:
- script.execute: wake_display
- lambda: |-
light_controls::demo = true;
light_controls::fallback.minimum = 2000;
light_controls::fallback.maximum = 6500;
light_controls::fallback.kelvin = 3000;
light_controls::fallback.hue = 200;
light_controls::open("diagnostic", true, true, 75);
lv_label_set_text(id(color_detail_title), "Voorbeeld lamp");
- lvgl.widget.hide: color_close_button
- lvgl.widget.hide: color_done_button
- lvgl.widget.show: color_detail_overlay
- delay: 40s
- lvgl.widget.hide: color_detail_overlay
- lvgl.widget.show: color_close_button
- lvgl.widget.show: color_done_button
- lambda: 'light_controls::demo = false; light_controls::fallback = light_controls::State{};'
- id: open_color_detail
mode: restart
then:
- lambda: |-
lv_label_set_text(id(color_detail_title), id(active_title).c_str());
light_controls::open(id(active_entity), id(active_supports_color), id(active_supports_temperature), id(active_brightness_pct));
- lvgl.widget.hide: brightness_overlay
- lvgl.widget.show: color_detail_overlay
- id: color_brightness_commit
parameters:
value: int
then:
- lambda: 'id(active_brightness_pct) = value;'
- if:
condition:
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && id(active_entity).rfind("light.", 0) == 0;'
then:
- homeassistant.action:
action: light.turn_on
data:
entity_id: !lambda "return id(active_entity);"
brightness_pct: !lambda "return value;"
- id: set_light_hue
parameters:
value: int
then:
- if:
condition:
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && id(active_supports_color) && id(active_entity).rfind("light.", 0) == 0;'
then:
- homeassistant.action:
action: light.turn_on
data:
entity_id: !lambda "return id(active_entity);"
data_template:
hs_color: "{{ [hue | int, 100] }}"
variables:
hue: !lambda "return light_controls::clamp(value, 0, 360);"
- id: set_light_color_temperature
parameters:
kelvin: int
then:
- if:
condition:
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && id(active_supports_temperature) && id(active_entity).rfind("light.", 0) == 0 && light_controls::active->temperature_ready();'
then:
- homeassistant.action:
action: light.turn_on
data:
entity_id: !lambda "return id(active_entity);"
color_temp_kelvin: !lambda "return light_controls::clamp(kelvin, light_controls::active->minimum, light_controls::active->maximum);"
- id: slider_preview
parameters:
value: int
then:
- lambda: |-
char buf[20];
snprintf(buf, sizeof(buf), "%d%s", value, id(active_value_suffix).c_str());
lv_label_set_text(id(overlay_value), buf);
int pct = value;
if (pct < 0) pct = 0;
if (pct > 100) pct = 100;
std::string value_text;
if (id(active_control_kind) == "cover_position") {
std::string closed_label = id(active_label_off).c_str();
std::string open_label = id(active_label_on).c_str();
if (pct <= 0) {
value_text = closed_label;
} else if (pct >= 100) {
value_text = open_label;
} else {
char buf[40];
snprintf(buf, sizeof(buf), "%s - %d%%", open_label.c_str(), pct);
value_text = buf;
}
} else {
if (pct <= 0) {
value_text = id(active_label_off).c_str();
} else {
char buf[20];
snprintf(buf, sizeof(buf), "%d%s", pct, id(active_value_suffix).c_str());
value_text = buf;
}
}
lv_label_set_text(id(overlay_value), value_text.c_str());
- id: slider_commit
parameters:
value: int
then:
- if:
condition:
lambda: |-
return id(active_control_kind) == "light_brightness";
then:
- homeassistant.action:
action: light.turn_on
data:
entity_id: !lambda "return id(active_entity);"
brightness_pct: !lambda "return value;"
else:
- if:
condition:
lambda: |-
return id(active_control_kind) == "fan_percentage";
then:
- if:
condition:
lambda: "return value <= 0;"
then:
- homeassistant.action:
action: fan.turn_off
data:
entity_id: !lambda "return id(active_entity);"
else:
- homeassistant.action:
action: fan.turn_on
data:
entity_id: !lambda "return id(active_entity);"
- delay: 150ms
- homeassistant.action:
action: fan.set_percentage
data:
entity_id: !lambda "return id(active_entity);"
percentage: !lambda "return value;"
else:
- if:
condition:
lambda: |-
return id(active_control_kind) == "cover_position";
then:
- homeassistant.action:
action: cover.set_cover_position
data:
entity_id: !lambda "return id(active_entity);"
position: !lambda "return value;"
# ----------------------------------------------------------
# do_tile_action
#
# Centralized handler for executing Home Assistant actions
# triggered by tile taps.
#
# Parameters
# - tile_type: light | fan | switch | scene | script | cover
# - tap_action: auto | toggle | activate | fan_toggle_preset
# - service: optional Home Assistant service override
# - entity_id: target entity
# - param_key / param_val: optional parameters for custom services
# - was_on: previous entity state (pass false for longpress actions)
#
# Behavior
#
# toggle
# -> toggles the entity
# light.toggle / fan.toggle / switch.toggle / cover.open_cover|close_cover (based on was_on)
#
# activate
# -> activates entity
# scene.turn_on / script.turn_on / light.turn_on / fan.turn_on / cover.open_cover
#
# auto
# -> resolves automatically based on tile_type:
# light/switch/fan/cover -> toggle
# scene/script -> activate
#
# fan_toggle_preset
# -> if fan was ON: fan.turn_off
# -> if fan was OFF:
# fan.turn_on
# wait 150ms
# fan.set_preset_mode(param_val)
#
# Custom service
# If "service" is set, it will be called directly.
# param_key / param_val may be used for parameters
# like preset_mode, brightness_pct, percentage, etc.
# ----------------------------------------------------------
- id: do_tile_action
mode: queued
parameters:
tile_type: string
tap_action: string
service: string
entity_id: string
param_key: string
param_val: string
was_on: bool
then:
- if:
condition:
lambda: |-
if (std::string("${DIRECT_ACTIONS}") != "true") return false;
// Debounce: zelfde entity binnen 600ms genegeerd. Voorkomt dat
// touch-ruis dezelfde HA-actie meerdere keren afvuurt (zie
// globals hierboven voor waarom dit nodig is).
uint32_t now = millis();
if (entity_id == id(last_action_entity) && (now - id(last_action_ms)) < 600) {
ESP_LOGD("tile", "do_tile_action: debounced repeat for %s (%ums na vorige)",
entity_id.c_str(), now - id(last_action_ms));
return false;
}
id(last_action_entity) = entity_id;
id(last_action_ms) = now;
return true;
then:
- lambda: |-
ESP_LOGD("tile", "do_tile_action type=%s action=%s service=%s entity=%s key=%s val=%s was_on=%d",
tile_type.c_str(), tap_action.c_str(), service.c_str(), entity_id.c_str(),
param_key.c_str(), param_val.c_str(), (int)was_on);
- if:
condition:
lambda: |-
return tap_action == "fan_toggle_preset";
then:
- if:
condition:
lambda: |-
return was_on;
then:
- homeassistant.action:
action: fan.turn_off
data:
entity_id: !lambda 'return entity_id;'
else:
- homeassistant.action:
action: fan.turn_on
data:
entity_id: !lambda 'return entity_id;'
- delay: 150ms
- homeassistant.action:
action: fan.set_preset_mode
data:
entity_id: !lambda 'return entity_id;'
preset_mode: !lambda 'return param_val;'
else:
- if:
condition:
lambda: |-
std::string resolved = tap_action;
if (resolved == "auto") {
if (tile_type == "scene" || tile_type == "script") resolved = "activate";
else resolved = "toggle";
}
return resolved == "toggle";
then:
- if:
condition:
lambda: |-
return service.size() > 0;
then:
- homeassistant.action:
action: !lambda 'return service;'
data:
entity_id: !lambda 'return entity_id;'
else:
- if:
condition:
lambda: |-
return tile_type == "light";
then:
- homeassistant.action:
action: light.toggle
data:
entity_id: !lambda 'return entity_id;'
else:
- if:
condition:
lambda: |-
return tile_type == "fan";
then:
- homeassistant.action:
action: fan.toggle
data:
entity_id: !lambda 'return entity_id;'
else:
- if:
condition:
lambda: |-
return tile_type == "switch";
then:
- homeassistant.action:
action: switch.toggle
data:
entity_id: !lambda 'return entity_id;'
else:
- if:
condition:
lambda: |-
return tile_type == "cover";
then:
- homeassistant.action:
action: cover.set_cover_position
data:
entity_id: !lambda 'return entity_id;'
position: !lambda |-
return was_on ? 0 : 100;
else:
- if:
condition:
lambda: |-
std::string resolved = tap_action;
if (resolved == "auto") {
if (tile_type == "scene" || tile_type == "script") resolved = "activate";
else resolved = "toggle";
}
return resolved == "activate";
then:
- if:
condition:
lambda: |-
return service.size() > 0;
then:
- if:
condition:
lambda: |-
return param_key == "preset_mode";
then:
- homeassistant.action:
action: !lambda 'return service;'
data:
entity_id: !lambda 'return entity_id;'
preset_mode: !lambda 'return param_val;'
else:
- if:
condition:
lambda: |-
return param_key == "brightness_pct";
then:
- homeassistant.action:
action: !lambda 'return service;'
data:
entity_id: !lambda 'return entity_id;'
brightness_pct: !lambda 'return atoi(param_val.c_str());'
else:
- if:
condition:
lambda: |-
return param_key == "percentage";
then:
- homeassistant.action:
action: !lambda 'return service;'
data:
entity_id: !lambda 'return entity_id;'
percentage: !lambda 'return atoi(param_val.c_str());'
else:
- if:
condition:
lambda: |-
return param_key == "option";
then:
- homeassistant.action:
action: !lambda 'return service;'
data:
entity_id: !lambda 'return entity_id;'
option: !lambda 'return param_val;'
else:
- homeassistant.action:
action: !lambda 'return service;'
data:
entity_id: !lambda 'return entity_id;'
else:
- if:
condition:
lambda: |-
return tile_type == "scene";
then:
- homeassistant.action:
action: scene.turn_on
data:
entity_id: !lambda 'return entity_id;'
else:
- if:
condition:
lambda: |-
return tile_type == "script";
then:
- homeassistant.action:
action: script.turn_on
data:
entity_id: !lambda 'return entity_id;'
else:
- if:
condition:
lambda: |-
return tile_type == "light";
then:
- homeassistant.action:
action: light.turn_on
data:
entity_id: !lambda 'return entity_id;'
else:
- if:
condition:
lambda: |-
return tile_type == "fan";
then:
- homeassistant.action:
action: fan.turn_on
data:
entity_id: !lambda 'return entity_id;'
else:
- if:
condition:
lambda: |-
return tile_type == "switch";
then:
- homeassistant.action:
action: switch.turn_on
data:
entity_id: !lambda 'return entity_id;'
else:
- if:
condition:
lambda: |-
return tile_type == "cover";
then:
- homeassistant.action:
action: cover.open_cover
data:
entity_id: !lambda 'return entity_id;'
- id: open_value_overlay
parameters:
title: string
entity: string
tile_type: string
longpress_mode: string
was_on: bool
off_value: int
brightness_value: float
percentage_value: float
position_value: float
icon_text: string
slider_bg_color: uint32_t
slider_fill_color: uint32_t
slider_icon_color: uint32_t
label_off: string
label_on: string
supported_color_modes: string
climate_current_temperature: float
climate_target_temperature: float
climate_humidity: float
climate_mode: string
climate_supported_modes: string
climate_min_temp: string
climate_max_temp: string
climate_step: string
then:
- lambda: |-
// Accentkleur uit het Home Assistant-domein: het stuk voor de punt in
// het entity_id. script/scene/button vuur je af (ACCENT_ACTION), een
// light is een light (ACCENT_LIGHT), de rest is apparaat.
// LET OP: deze helper staat twee keer in dit bestand — hier en in
// open_value_overlay. Pas ze samen aan.
auto accent_for = [](const std::string& entity_id) -> uint32_t {
std::string d = entity_id.substr(0, entity_id.find('.'));
if (d == "script" || d == "scene" || d == "button" ||
d == "input_button" || d == "automation") return ${ACCENT_ACTION};
if (d == "light") return ${ACCENT_LIGHT};
return ${ACCENT_DEVICE};
};
// De sliderkleur komt van de tegel, dus die kan ook ${ACCENT_AUTO} zijn.
uint32_t fill_color = (slider_fill_color == ${ACCENT_AUTO})
? accent_for(entity) : slider_fill_color;
std::string mode = longpress_mode;
auto contains_mode = [](const std::string& modes, const std::string& needle) {
return modes.find(needle) != std::string::npos;
};
if (mode == "auto") {
if (tile_type == "light") mode = "brightness";
else if (tile_type == "fan") mode = "percentage";
else if (tile_type == "cover") mode = "cover_position";
else if (tile_type == "climate") mode = "climate_temperature";
else mode = "none";
}
if (mode == "none") return;
id(active_entity) = entity;
id(active_value_suffix) = " %";
id(active_label_off) = label_off;
id(active_label_on) = label_on;
id(active_title) = title;
id(active_icon_text) = icon_text;
id(active_supports_color) = false;
id(active_supports_temperature) = false;
lv_obj_add_flag(id(overlay_color_button), LV_OBJ_FLAG_HIDDEN);
if (mode == "climate_temperature") {
auto to_x10 = [](float value, int fallback) -> int {
if (isnan(value)) return fallback;
return (int) lround(value * 10.0f);
};
auto parse_x10 = [](const std::string& value, int fallback) -> int {
if (value.empty()) return fallback;
return (int) lround(atof(value.c_str()) * 10.0f);
};
id(climate_edit_pending) = false;
id(active_control_kind) = "climate_temperature";
id(active_climate_min_x10) = parse_x10(climate_min_temp, 50);
id(active_climate_max_x10) = parse_x10(climate_max_temp, 300);
id(active_climate_step_x10) = parse_x10(climate_step, 5);
if (id(active_climate_step_x10) < 5) id(active_climate_step_x10) = 5;
if (id(active_climate_max_x10) <= id(active_climate_min_x10)) {
id(active_climate_min_x10) = 50;
id(active_climate_max_x10) = 300;
}
id(active_climate_current_x10) = to_x10(climate_current_temperature, 0);
int fallback_target = isnan(climate_current_temperature) ? 200 : id(active_climate_current_x10);
id(active_climate_target_x10) = to_x10(climate_target_temperature, fallback_target);
if (id(active_climate_target_x10) < id(active_climate_min_x10)) id(active_climate_target_x10) = id(active_climate_min_x10);
if (id(active_climate_target_x10) > id(active_climate_max_x10)) id(active_climate_target_x10) = id(active_climate_max_x10);
id(active_climate_target_x10) = id(active_climate_min_x10) + (int) lround((id(active_climate_target_x10) - id(active_climate_min_x10)) / (float) id(active_climate_step_x10)) * id(active_climate_step_x10);
if (id(active_climate_target_x10) < id(active_climate_min_x10)) id(active_climate_target_x10) = id(active_climate_min_x10);
if (id(active_climate_target_x10) > id(active_climate_max_x10)) id(active_climate_target_x10) = id(active_climate_max_x10);
id(active_climate_humidity) = isnan(climate_humidity) ? -1 : (int) lround(climate_humidity);
id(active_climate_mode) = climate_mode.empty() ? std::string("Auto") : climate_mode;
id(active_climate_supported_modes) = climate_supported_modes;
lv_label_set_text(id(climate_detail_title), title.c_str());
char current_buf[48];
if (isnan(climate_current_temperature)) {
snprintf(current_buf, sizeof(current_buf), "Current: --");
} else if (id(active_climate_humidity) >= 0) {
snprintf(current_buf, sizeof(current_buf), "Current: %.1f° - Humidity: %d %%", climate_current_temperature, id(active_climate_humidity));
} else {
snprintf(current_buf, sizeof(current_buf), "Current: %.1f°", climate_current_temperature);
}
lv_label_set_text(id(climate_current_label), current_buf);
char target_buf[16];
snprintf(target_buf, sizeof(target_buf), "%.1f°", id(active_climate_target_x10) / 10.0f);
lv_label_set_text(id(climate_target_label), target_buf);
lv_label_set_text(id(climate_mode_label), id(active_climate_mode).c_str());
lv_arc_set_range(id(climate_arc), id(active_climate_min_x10), id(active_climate_max_x10));
lv_arc_set_value(id(climate_arc), id(active_climate_target_x10));
if (atoi("${DISPLAY_W}") < atoi("${DISPLAY_H}")) {
lv_obj_set_size(id(climate_arc), 294, 294);
lv_obj_set_y(id(climate_arc), 16);
lv_obj_set_y(id(climate_current_label), 96);
lv_obj_set_y(id(climate_minus_button), -124);
lv_obj_set_y(id(climate_plus_button), -124);
lv_obj_set_y(id(climate_mode_pill), -36);
} else {
lv_obj_set_size(id(climate_arc), 264, 264);
lv_obj_set_y(id(climate_arc), 36);
lv_obj_set_y(id(climate_current_label), 80);
lv_obj_set_y(id(climate_minus_button), -28);
lv_obj_set_y(id(climate_plus_button), -28);
lv_obj_set_y(id(climate_mode_pill), -30);
}
lv_obj_add_flag(id(brightness_overlay), LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(id(climate_detail_overlay), LV_OBJ_FLAG_HIDDEN);
return;
}
int pct = off_value;
if (mode == "brightness") {
id(active_control_kind) = "light_brightness";
if (was_on) {
if (isnan(brightness_value)) pct = 100;
else pct = (int) lround((brightness_value / 255.0f) * 100.0f);
}
} else if (mode == "percentage") {
id(active_control_kind) = "fan_percentage";
if (was_on) {
if (isnan(percentage_value)) pct = 0;
else pct = (int) lround(percentage_value);
}
} else if (mode == "cover_position") {
id(active_control_kind) = "cover_position";
if (isnan(position_value)) pct = was_on ? 100 : 0;
else pct = (int) lround(position_value);
} else {
return;
}
if (pct < 0) pct = 0;
if (pct > 100) pct = 100;
id(active_brightness_pct) = pct;
if (tile_type == "light" && std::string("${DIRECT_ACTIONS}") == "true") {
id(active_supports_color) =
contains_mode(supported_color_modes, "hs") ||
contains_mode(supported_color_modes, "rgb") ||
contains_mode(supported_color_modes, "rgbw") ||
contains_mode(supported_color_modes, "rgbww") ||
contains_mode(supported_color_modes, "xy");
id(active_supports_temperature) = contains_mode(supported_color_modes, "color_temp");
if (id(active_supports_color) || id(active_supports_temperature)) {
lv_obj_clear_flag(id(overlay_color_button), LV_OBJ_FLAG_HIDDEN);
}
}
if (tile_type == "cover") {
lv_obj_set_style_bg_color(id(overlay_slider), lv_color_hex(fill_color), LV_PART_MAIN);
lv_obj_set_style_bg_color(id(overlay_slider), lv_color_hex(slider_bg_color), LV_PART_INDICATOR);
lv_obj_set_style_bg_opa(id(overlay_slider), LV_OPA_COVER, LV_PART_MAIN);
}
else{
lv_obj_set_style_bg_color(id(overlay_slider), lv_color_hex(slider_bg_color), LV_PART_MAIN);
lv_obj_set_style_bg_color(id(overlay_slider), lv_color_hex(fill_color), LV_PART_INDICATOR);
lv_obj_set_style_bg_opa(id(overlay_slider), LV_OPA_COVER, LV_PART_MAIN);
}
lv_obj_set_style_radius(id(overlay_slider), 28, LV_PART_MAIN);
lv_obj_set_style_radius(id(overlay_slider), 0, LV_PART_INDICATOR);
lv_label_set_text(id(overlay_title), title.c_str());
lv_label_set_text(id(overlay_icon), icon_text.c_str());
lv_slider_set_value(id(overlay_slider), pct, LV_ANIM_OFF);
std::string value_text;
if (tile_type == "cover") {
if (pct <= 0) {
value_text = label_off;
} else if (pct >= 100) {
value_text = label_on;
} else {
char buf[40];
snprintf(buf, sizeof(buf), "%s - %d%%", label_on.c_str(), pct);
value_text = buf;
}
} else {
if (pct <= 0) {
value_text = label_off;
} else {
char buf[20];
snprintf(buf, sizeof(buf), "%d%s", pct, id(active_value_suffix).c_str());
value_text = buf;
}
}
lv_label_set_text(id(overlay_value), value_text.c_str());
lv_obj_set_style_bg_opa(id(overlay_slider), LV_OPA_COVER, LV_PART_INDICATOR);
lv_obj_set_style_text_color(id(overlay_icon), lv_color_hex(slider_icon_color), 0);
- if:
condition:
lambda: 'return id(active_control_kind) != "climate_temperature";'
then:
- if:
condition:
lambda: 'return id(active_supports_color) || id(active_supports_temperature);'
then:
- script.execute: open_color_detail
else:
- lvgl.widget.show: brightness_overlay
# UI refresh (values + time + styles) + render
- id: ui_refresh
mode: single
then:
- delay: 100ms
- lambda: |-
if (runtime_tiles::enabled) {
runtime_tiles::render(id(lbl_room));
auto now = id(homeassistant_time).now();
if (now.is_valid()) runtime_tiles::label(id(lbl_time), now.strftime("%H:%M"));
return;
}
auto set_lbl = [](lv_obj_t* lbl, const std::string& s) {
if (lbl && s != lv_label_get_text(lbl)) lv_label_set_text(lbl, s.c_str());
};
auto set_value_auto = [&](lv_obj_t* lbl,
bool on,
const std::string& tile_type,
const std::string& value_mode,
float brightness,
float percentage,
const std::string& preset,
float climate_current_temperature,
float climate_target_temperature,
float climate_humidity,
const std::string& label_on,
const std::string& label_off) {
std::string mode = value_mode;
if (mode == "auto" || mode.empty()) {
if (tile_type == "light") {
mode = "brightness";
} else if (tile_type == "fan") {
mode = preset.empty() ? "percentage" : "preset";
} else if (tile_type == "climate") {
mode = "climate";
} else {
mode = "text";
}
}
if (mode == "brightness") {
if (!on) {
set_lbl(lbl, label_off);
return;
}
if (isnan(brightness)) {
set_lbl(lbl, "100 %");
return;
}
int pct = (int) lround((brightness / 255.0f) * 100.0f);
char buf[8];
snprintf(buf, sizeof(buf), "%d %%", pct);
set_lbl(lbl, std::string(buf));
return;
}
if (mode == "percentage") {
if (!on) {
set_lbl(lbl, label_off);
return;
}
int pct = isnan(percentage) ? 0 : (int) lround(percentage);
char buf[8];
snprintf(buf, sizeof(buf), "%d %%", pct);
set_lbl(lbl, std::string(buf));
return;
}
if (mode == "preset") {
if (!on) {
set_lbl(lbl, label_off);
return;
}
if (!preset.empty()) {
set_lbl(lbl, preset);
} else {
set_lbl(lbl, label_on);
}
return;
}
if (mode == "climate") {
if (!on) {
set_lbl(lbl, label_off);
return;
}
float temp = isnan(climate_target_temperature) ? climate_current_temperature : climate_target_temperature;
if (isnan(temp)) {
set_lbl(lbl, label_on);
return;
}
char buf[16];
snprintf(buf, sizeof(buf), "%.1f °", temp);
set_lbl(lbl, std::string(buf));
return;
}
if (mode == "text") {
if (tile_type == "scene" || tile_type == "script") {
set_lbl(lbl, label_on);
} else {
set_lbl(lbl, on ? label_on : label_off);
}
return;
}
set_lbl(lbl, "—");
};
// active_state niet leeg? Dan telt alleen die exacte HA-state als "aan".
// Zo kan een input_select ("Vol"/"Leeg") ook een tegel laten oplichten.
auto is_active = [](const std::string& tile_type, const std::string& state,
const std::string& active_state = std::string()) {
if (!active_state.empty()) return state == active_state;
if (tile_type == "climate") {
return !state.empty() && state != "off" && state != "unavailable" && state != "unknown";
}
if (tile_type == "vacuum") return state == "cleaning" || state == "returning";
if (tile_type == "scene" || tile_type == "script") return true;
return state == "on";
};
// Cover-aware active state: "closed" = off, anything else (open/opening/closing) = on
bool t1_on = (std::string("${TILE1_TYPE}") == "cover") ? (id(tile1_cover_state).state != "closed") : is_active("${TILE1_TYPE}", std::string(id(ha_state_tile1).state.c_str()), "${TILE1_ACTIVE_STATE}");
bool t2_on = (std::string("${TILE2_TYPE}") == "cover") ? (id(tile2_cover_state).state != "closed") : is_active("${TILE2_TYPE}", std::string(id(ha_state_tile2).state.c_str()), "${TILE2_ACTIVE_STATE}");
bool t3_on = (std::string("${TILE3_TYPE}") == "cover") ? (id(tile3_cover_state).state != "closed") : is_active("${TILE3_TYPE}", std::string(id(ha_state_tile3).state.c_str()), "${TILE3_ACTIVE_STATE}");
bool t4_on = (std::string("${TILE4_TYPE}") == "cover") ? (id(tile4_cover_state).state != "closed") : is_active("${TILE4_TYPE}", std::string(id(ha_state_tile4).state.c_str()), "${TILE4_ACTIVE_STATE}");
bool t5_on = (std::string("${TILE5_TYPE}") == "cover") ? (id(tile5_cover_state).state != "closed") : is_active("${TILE5_TYPE}", std::string(id(ha_state_tile5).state.c_str()), "${TILE5_ACTIVE_STATE}");
bool t6_on = (std::string("${TILE6_TYPE}") == "cover") ? (id(tile6_cover_state).state != "closed") : is_active("${TILE6_TYPE}", std::string(id(ha_state_tile6).state.c_str()), "${TILE6_ACTIVE_STATE}");
// Tegel 7/8 hebben geen cover_state-sensor, dus geen cover-tak nodig.
bool t7_on = is_active("${TILE7_TYPE}", std::string(id(ha_state_tile7).state.c_str()), "${TILE7_ACTIVE_STATE}");
bool t8_on = is_active("${TILE8_TYPE}", std::string(id(ha_state_tile8).state.c_str()), "${TILE8_ACTIVE_STATE}");
bool t9_on = is_active("${TILE9_TYPE}", std::string(id(ha_state_tile9).state.c_str()), "${TILE9_ACTIVE_STATE}");
bool t10_on = is_active("${TILE10_TYPE}", std::string(id(ha_state_tile10).state.c_str()), "${TILE10_ACTIVE_STATE}");
set_value_auto(id(t1_value), t1_on, "${TILE1_TYPE}", "${TILE1_VALUE_MODE}",
id(tile1_brightness).state, id(tile1_percentage).state, std::string(id(tile1_preset).state.c_str()),
id(tile1_climate_current_temperature).state, id(tile1_climate_target_temperature).state, id(tile1_climate_humidity).state,
"${TILE1_LABEL_ON}", "${TILE1_LABEL_OFF}");
set_value_auto(id(t2_value), t2_on, "${TILE2_TYPE}", "${TILE2_VALUE_MODE}",
id(tile2_brightness).state, id(tile2_percentage).state, std::string(id(tile2_preset).state.c_str()),
id(tile2_climate_current_temperature).state, id(tile2_climate_target_temperature).state, id(tile2_climate_humidity).state,
"${TILE2_LABEL_ON}", "${TILE2_LABEL_OFF}");
set_value_auto(id(t3_value), t3_on, "${TILE3_TYPE}", "${TILE3_VALUE_MODE}",
id(tile3_brightness).state, id(tile3_percentage).state, std::string(id(tile3_preset).state.c_str()),
id(tile3_climate_current_temperature).state, id(tile3_climate_target_temperature).state, id(tile3_climate_humidity).state,
"${TILE3_LABEL_ON}", "${TILE3_LABEL_OFF}");
set_value_auto(id(t4_value), t4_on, "${TILE4_TYPE}", "${TILE4_VALUE_MODE}",
id(tile4_brightness).state, id(tile4_percentage).state, std::string(id(tile4_preset).state.c_str()),
id(tile4_climate_current_temperature).state, id(tile4_climate_target_temperature).state, id(tile4_climate_humidity).state,
"${TILE4_LABEL_ON}", "${TILE4_LABEL_OFF}");
set_value_auto(id(t5_value), t5_on, "${TILE5_TYPE}", "${TILE5_VALUE_MODE}",
id(tile5_brightness).state, id(tile5_percentage).state, std::string(id(tile5_preset).state.c_str()),
id(tile5_climate_current_temperature).state, id(tile5_climate_target_temperature).state, id(tile5_climate_humidity).state,
"${TILE5_LABEL_ON}", "${TILE5_LABEL_OFF}");
set_value_auto(id(t6_value), t6_on, "${TILE6_TYPE}", "${TILE6_VALUE_MODE}",
id(tile6_brightness).state, id(tile6_percentage).state, std::string(id(tile6_preset).state.c_str()),
id(tile6_climate_current_temperature).state, id(tile6_climate_target_temperature).state, id(tile6_climate_humidity).state,
"${TILE6_LABEL_ON}", "${TILE6_LABEL_OFF}");
// Tegel 7: light met alleen brightness. Tegel 8: puur tekst (Vol/Leeg).
set_value_auto(id(t7_value), t7_on, "${TILE7_TYPE}", "${TILE7_VALUE_MODE}",
id(tile7_brightness).state, NAN, std::string(),
NAN, NAN, NAN,
"${TILE7_LABEL_ON}", "${TILE7_LABEL_OFF}");
set_value_auto(id(t8_value), t8_on, "${TILE8_TYPE}", "${TILE8_VALUE_MODE}",
NAN, NAN, std::string(),
NAN, NAN, NAN,
"${TILE8_LABEL_ON}", "${TILE8_LABEL_OFF}");
// Tegel 9: kleurenlamp (brightness). Tegel 10: puur tekst (Aan/Uit).
set_value_auto(id(t9_value), t9_on, "${TILE9_TYPE}", "${TILE9_VALUE_MODE}",
id(tile9_brightness).state, NAN, std::string(),
NAN, NAN, NAN,
"${TILE9_LABEL_ON}", "${TILE9_LABEL_OFF}");
set_value_auto(id(t10_value), t10_on, "${TILE10_TYPE}", "${TILE10_VALUE_MODE}",
NAN, NAN, std::string(),
NAN, NAN, NAN,
"${TILE10_LABEL_ON}", "${TILE10_LABEL_OFF}");
// Missing state is not an off state. Preserve meaningful vacuum states.
const char* vacuum_state = id(ha_state_tile6).state.c_str();
if (std::string("${TILE6_TYPE}") == "vacuum") {
std::string state(vacuum_state);
if (state == "cleaning") set_lbl(id(t6_value), "Bezig");
else if (state == "docked") set_lbl(id(t6_value), "In dock");
else if (state == "returning") set_lbl(id(t6_value), "Naar dock");
else if (state == "paused") set_lbl(id(t6_value), "Gepauzeerd");
else if (state == "error") set_lbl(id(t6_value), "Fout");
else if (!state.empty()) set_lbl(id(t6_value), state);
}
if (!id(ha_state_tile1).has_state() || id(ha_state_tile1).state == "unknown" || id(ha_state_tile1).state == "unavailable")
set_lbl(id(t1_value), "Niet beschikbaar");
if (!id(ha_state_tile2).has_state() || id(ha_state_tile2).state == "unknown" || id(ha_state_tile2).state == "unavailable")
set_lbl(id(t2_value), "Niet beschikbaar");
if (!id(ha_state_tile3).has_state() || id(ha_state_tile3).state == "unknown" || id(ha_state_tile3).state == "unavailable")
set_lbl(id(t3_value), "Niet beschikbaar");
if (!id(ha_state_tile4).has_state() || id(ha_state_tile4).state == "unknown" || id(ha_state_tile4).state == "unavailable")
set_lbl(id(t4_value), "Niet beschikbaar");
if (!id(ha_state_tile5).has_state() || id(ha_state_tile5).state == "unknown" || id(ha_state_tile5).state == "unavailable")
set_lbl(id(t5_value), "Niet beschikbaar");
if (!id(ha_state_tile6).has_state() || id(ha_state_tile6).state == "unknown" || id(ha_state_tile6).state == "unavailable")
set_lbl(id(t6_value), "Niet beschikbaar");
if (!id(ha_state_tile7).has_state() || id(ha_state_tile7).state == "unknown" || id(ha_state_tile7).state == "unavailable")
set_lbl(id(t7_value), "Niet beschikbaar");
if (!id(ha_state_tile8).has_state() || id(ha_state_tile8).state == "unknown" || id(ha_state_tile8).state == "unavailable")
set_lbl(id(t8_value), "Niet beschikbaar");
if (!id(ha_state_tile9).has_state() || id(ha_state_tile9).state == "unknown" || id(ha_state_tile9).state == "unavailable")
set_lbl(id(t9_value), "Niet beschikbaar");
if (!id(ha_state_tile10).has_state() || id(ha_state_tile10).state == "unknown" || id(ha_state_tile10).state == "unavailable")
set_lbl(id(t10_value), "Niet beschikbaar");
if (id(active_control_kind) == "climate_temperature" && !lv_obj_has_flag(id(climate_detail_overlay), LV_OBJ_FLAG_HIDDEN)) {
auto label_for_climate_mode = [](std::string mode) {
for (char &c : mode) {
if (c >= 'A' && c <= 'Z') c = (char) (c - 'A' + 'a');
}
if (mode == "off") return std::string("Off");
if (mode == "heat") return std::string("Heat");
if (mode == "cool") return std::string("Cool");
if (mode == "heat_cool") return std::string("Heat/Cool");
if (mode == "auto") return std::string("Auto");
if (mode == "dry") return std::string("Dry");
if (mode == "fan_only") return std::string("Fan");
return mode.empty() ? std::string("Auto") : mode;
};
auto sync_open_climate = [&](const std::string& entity,
float current_temperature,
float target_temperature,
float humidity,
const std::string& climate_mode) {
if (id(active_entity) != entity) return;
auto to_x10 = [](float value, int fallback) -> int {
if (isnan(value)) return fallback;
return (int) lround(value * 10.0f);
};
id(active_climate_current_x10) = to_x10(current_temperature, id(active_climate_current_x10));
id(active_climate_humidity) = isnan(humidity) ? -1 : (int) lround(humidity);
if (!climate_mode.empty() && !id(touch_down) &&
!(id(climate_edit_pending) && millis() - id(climate_edit_ms) < 3000)) {
id(active_climate_mode) = climate_mode;
lv_label_set_text(id(climate_mode_label), label_for_climate_mode(climate_mode).c_str());
}
const bool editing = id(touch_down) ||
(id(climate_edit_pending) && millis() - id(climate_edit_ms) < 3000);
if (!editing) id(climate_edit_pending) = false;
int target_x10 = editing ? id(active_climate_target_x10) :
to_x10(target_temperature, id(active_climate_target_x10));
if (target_x10 < id(active_climate_min_x10)) target_x10 = id(active_climate_min_x10);
if (target_x10 > id(active_climate_max_x10)) target_x10 = id(active_climate_max_x10);
int step_x10 = id(active_climate_step_x10);
if (step_x10 < 5) step_x10 = 5;
target_x10 = id(active_climate_min_x10) + (int) lround((target_x10 - id(active_climate_min_x10)) / (float) step_x10) * step_x10;
if (target_x10 < id(active_climate_min_x10)) target_x10 = id(active_climate_min_x10);
if (target_x10 > id(active_climate_max_x10)) target_x10 = id(active_climate_max_x10);
id(active_climate_target_x10) = target_x10;
char current_buf[48];
if (isnan(current_temperature)) {
snprintf(current_buf, sizeof(current_buf), "Current: --");
} else if (id(active_climate_humidity) >= 0) {
snprintf(current_buf, sizeof(current_buf), "Current: %.1f° - Humidity: %d %%", current_temperature, id(active_climate_humidity));
} else {
snprintf(current_buf, sizeof(current_buf), "Current: %.1f°", current_temperature);
}
lv_label_set_text(id(climate_current_label), current_buf);
char target_buf[16];
snprintf(target_buf, sizeof(target_buf), "%.1f°", id(active_climate_target_x10) / 10.0f);
lv_label_set_text(id(climate_target_label), target_buf);
lv_arc_set_value(id(climate_arc), id(active_climate_target_x10));
};
if (std::string("${TILE1_TYPE}") == "climate") sync_open_climate("${TILE1_ENTITY}", id(tile1_climate_current_temperature).state, id(tile1_climate_target_temperature).state, id(tile1_climate_humidity).state, std::string(id(tile1_climate_mode).state.c_str()));
if (std::string("${TILE2_TYPE}") == "climate") sync_open_climate("${TILE2_ENTITY}", id(tile2_climate_current_temperature).state, id(tile2_climate_target_temperature).state, id(tile2_climate_humidity).state, std::string(id(tile2_climate_mode).state.c_str()));
if (std::string("${TILE3_TYPE}") == "climate") sync_open_climate("${TILE3_ENTITY}", id(tile3_climate_current_temperature).state, id(tile3_climate_target_temperature).state, id(tile3_climate_humidity).state, std::string(id(tile3_climate_mode).state.c_str()));
if (std::string("${TILE4_TYPE}") == "climate") sync_open_climate("${TILE4_ENTITY}", id(tile4_climate_current_temperature).state, id(tile4_climate_target_temperature).state, id(tile4_climate_humidity).state, std::string(id(tile4_climate_mode).state.c_str()));
if (std::string("${TILE5_TYPE}") == "climate") sync_open_climate("${TILE5_ENTITY}", id(tile5_climate_current_temperature).state, id(tile5_climate_target_temperature).state, id(tile5_climate_humidity).state, std::string(id(tile5_climate_mode).state.c_str()));
if (std::string("${TILE6_TYPE}") == "climate") sync_open_climate("${TILE6_ENTITY}", id(tile6_climate_current_temperature).state, id(tile6_climate_target_temperature).state, id(tile6_climate_humidity).state, std::string(id(tile6_climate_mode).state.c_str()));
}
{
auto now = id(homeassistant_time).now();
// Vóór de eerste tijd-sync is now() epoch 0 (1970) — dan geen
// misleidende "01:00" tonen, gewoon leeg laten tot er een
// echte tijd binnen is (na de eerste Home Assistant-tijdsynchronisatie).
if (now.is_valid()) {
char buf[10];
if (std::string("${TIME_24H}") == "true") {
snprintf(buf, sizeof(buf), "%02d:%02d", now.hour, now.minute);
} else {
int hour12 = now.hour % 12;
if (hour12 == 0) hour12 = 12;
snprintf(buf, sizeof(buf), "%d:%02d %s", hour12, now.minute, now.hour >= 12 ? "PM" : "AM");
}
set_lbl(id(lbl_time), std::string(buf));
}
}
// Accentkleur uit het Home Assistant-domein: het stuk voor de punt in
// het entity_id. script/scene/button vuur je af (ACCENT_ACTION), een
// light is een light (ACCENT_LIGHT), de rest is apparaat.
// LET OP: deze helper staat twee keer in dit bestand — hier en in
// open_value_overlay. Pas ze samen aan.
auto accent_for = [](const std::string& entity_id) -> uint32_t {
std::string d = entity_id.substr(0, entity_id.find('.'));
if (d == "script" || d == "scene" || d == "button" ||
d == "input_button" || d == "automation") return ${ACCENT_ACTION};
if (d == "light") return ${ACCENT_LIGHT};
return ${ACCENT_DEVICE};
};
auto apply_tile = [&](bool active,
const std::string& entity_id,
lv_obj_t* tile,
lv_obj_t* circle,
lv_obj_t* title,
lv_obj_t* value,
lv_obj_t* icon_lbl,
uint32_t circle_active,
uint32_t circle_disabled,
uint32_t icon_active,
uint32_t icon_disabled,
uint32_t bg_active,
uint32_t bg_disabled,
uint32_t title_active,
uint32_t title_disabled,
uint32_t value_active,
uint32_t value_disabled)
{
static lv_obj_t* cached_tiles[10] = {};
static bool cached_active[10] = {};
for (int i = 0; i < 10; ++i) {
if (cached_tiles[i] == tile) {
if (cached_active[i] == active) return;
cached_active[i] = active;
break;
}
if (cached_tiles[i] == nullptr) {
cached_tiles[i] = tile;
cached_active[i] = active;
break;
}
}
// ${ACCENT_AUTO} = "bepaal zelf"; een echte hexwaarde wint altijd.
uint32_t accent = accent_for(entity_id);
if (circle_active == ${ACCENT_AUTO}) circle_active = accent;
if (icon_disabled == ${ACCENT_AUTO}) icon_disabled = accent;
lv_obj_set_style_bg_color(tile, lv_color_hex(active ? bg_active : bg_disabled), 0);
lv_obj_set_style_bg_color(circle, lv_color_hex(active ? circle_active : circle_disabled), 0);
lv_obj_set_style_text_color(icon_lbl, lv_color_hex(active ? icon_active : icon_disabled), 0);
lv_obj_set_style_text_color(title, lv_color_hex(active ? title_active : title_disabled), 0);
lv_obj_set_style_text_color(value, lv_color_hex(active ? value_active : value_disabled), 0);
};
apply_tile(t1_on, "${TILE1_ENTITY}",
id(tile1), id(tile1_icon_circle), id(t1_title), id(t1_value), id(tile1_icon_lbl),
${TILE1_CIRCLE_ACTIVE_COLOR}, ${TILE1_CIRCLE_DISABLED_COLOR},
${TILE1_ICON_ACTIVE_COLOR}, ${TILE1_ICON_DISABLED_COLOR},
${TILE1_BG_ACTIVE_COLOR}, ${TILE1_BG_DISABLED_COLOR},
${TILE1_TITLE_ACTIVE_COLOR}, ${TILE1_TITLE_DISABLED_COLOR},
${TILE1_VALUE_ACTIVE_COLOR}, ${TILE1_VALUE_DISABLED_COLOR});
apply_tile(t2_on, "${TILE2_ENTITY}",
id(tile2), id(tile2_icon_circle), id(t2_title), id(t2_value), id(tile2_icon_lbl),
${TILE2_CIRCLE_ACTIVE_COLOR}, ${TILE2_CIRCLE_DISABLED_COLOR},
${TILE2_ICON_ACTIVE_COLOR}, ${TILE2_ICON_DISABLED_COLOR},
${TILE2_BG_ACTIVE_COLOR}, ${TILE2_BG_DISABLED_COLOR},
${TILE2_TITLE_ACTIVE_COLOR}, ${TILE2_TITLE_DISABLED_COLOR},
${TILE2_VALUE_ACTIVE_COLOR}, ${TILE2_VALUE_DISABLED_COLOR});
apply_tile(t3_on, "${TILE3_ENTITY}",
id(tile3), id(tile3_icon_circle), id(t3_title), id(t3_value), id(tile3_icon_lbl),
${TILE3_CIRCLE_ACTIVE_COLOR}, ${TILE3_CIRCLE_DISABLED_COLOR},
${TILE3_ICON_ACTIVE_COLOR}, ${TILE3_ICON_DISABLED_COLOR},
${TILE3_BG_ACTIVE_COLOR}, ${TILE3_BG_DISABLED_COLOR},
${TILE3_TITLE_ACTIVE_COLOR}, ${TILE3_TITLE_DISABLED_COLOR},
${TILE3_VALUE_ACTIVE_COLOR}, ${TILE3_VALUE_DISABLED_COLOR});
apply_tile(t4_on, "${TILE4_ENTITY}",
id(tile4), id(tile4_icon_circle), id(t4_title), id(t4_value), id(tile4_icon_lbl),
${TILE4_CIRCLE_ACTIVE_COLOR}, ${TILE4_CIRCLE_DISABLED_COLOR},
${TILE4_ICON_ACTIVE_COLOR}, ${TILE4_ICON_DISABLED_COLOR},
${TILE4_BG_ACTIVE_COLOR}, ${TILE4_BG_DISABLED_COLOR},
${TILE4_TITLE_ACTIVE_COLOR}, ${TILE4_TITLE_DISABLED_COLOR},
${TILE4_VALUE_ACTIVE_COLOR}, ${TILE4_VALUE_DISABLED_COLOR});
apply_tile(t5_on, "${TILE5_ENTITY}",
id(tile5), id(tile5_icon_circle), id(t5_title), id(t5_value), id(tile5_icon_lbl),
${TILE5_CIRCLE_ACTIVE_COLOR}, ${TILE5_CIRCLE_DISABLED_COLOR},
${TILE5_ICON_ACTIVE_COLOR}, ${TILE5_ICON_DISABLED_COLOR},
${TILE5_BG_ACTIVE_COLOR}, ${TILE5_BG_DISABLED_COLOR},
${TILE5_TITLE_ACTIVE_COLOR}, ${TILE5_TITLE_DISABLED_COLOR},
${TILE5_VALUE_ACTIVE_COLOR}, ${TILE5_VALUE_DISABLED_COLOR});
apply_tile(t6_on, "${TILE6_ENTITY}",
id(tile6), id(tile6_icon_circle), id(t6_title), id(t6_value), id(tile6_icon_lbl),
${TILE6_CIRCLE_ACTIVE_COLOR}, ${TILE6_CIRCLE_DISABLED_COLOR},
${TILE6_ICON_ACTIVE_COLOR}, ${TILE6_ICON_DISABLED_COLOR},
${TILE6_BG_ACTIVE_COLOR}, ${TILE6_BG_DISABLED_COLOR},
${TILE6_TITLE_ACTIVE_COLOR}, ${TILE6_TITLE_DISABLED_COLOR},
${TILE6_VALUE_ACTIVE_COLOR}, ${TILE6_VALUE_DISABLED_COLOR});
apply_tile(t7_on, "${TILE7_ENTITY}",
id(tile7), id(tile7_icon_circle), id(t7_title), id(t7_value), id(tile7_icon_lbl),
${TILE7_CIRCLE_ACTIVE_COLOR}, ${TILE7_CIRCLE_DISABLED_COLOR},
${TILE7_ICON_ACTIVE_COLOR}, ${TILE7_ICON_DISABLED_COLOR},
${TILE7_BG_ACTIVE_COLOR}, ${TILE7_BG_DISABLED_COLOR},
${TILE7_TITLE_ACTIVE_COLOR}, ${TILE7_TITLE_DISABLED_COLOR},
${TILE7_VALUE_ACTIVE_COLOR}, ${TILE7_VALUE_DISABLED_COLOR});
apply_tile(t8_on, "${TILE8_ENTITY}",
id(tile8), id(tile8_icon_circle), id(t8_title), id(t8_value), id(tile8_icon_lbl),
${TILE8_CIRCLE_ACTIVE_COLOR}, ${TILE8_CIRCLE_DISABLED_COLOR},
${TILE8_ICON_ACTIVE_COLOR}, ${TILE8_ICON_DISABLED_COLOR},
${TILE8_BG_ACTIVE_COLOR}, ${TILE8_BG_DISABLED_COLOR},
${TILE8_TITLE_ACTIVE_COLOR}, ${TILE8_TITLE_DISABLED_COLOR},
${TILE8_VALUE_ACTIVE_COLOR}, ${TILE8_VALUE_DISABLED_COLOR});
apply_tile(t9_on, "${TILE9_ENTITY}",
id(tile9), id(tile9_icon_circle), id(t9_title), id(t9_value), id(tile9_icon_lbl),
${TILE9_CIRCLE_ACTIVE_COLOR}, ${TILE9_CIRCLE_DISABLED_COLOR},
${TILE9_ICON_ACTIVE_COLOR}, ${TILE9_ICON_DISABLED_COLOR},
${TILE9_BG_ACTIVE_COLOR}, ${TILE9_BG_DISABLED_COLOR},
${TILE9_TITLE_ACTIVE_COLOR}, ${TILE9_TITLE_DISABLED_COLOR},
${TILE9_VALUE_ACTIVE_COLOR}, ${TILE9_VALUE_DISABLED_COLOR});
apply_tile(t10_on, "${TILE10_ENTITY}",
id(tile10), id(tile10_icon_circle), id(t10_title), id(t10_value), id(tile10_icon_lbl),
${TILE10_CIRCLE_ACTIVE_COLOR}, ${TILE10_CIRCLE_DISABLED_COLOR},
${TILE10_ICON_ACTIVE_COLOR}, ${TILE10_ICON_DISABLED_COLOR},
${TILE10_BG_ACTIVE_COLOR}, ${TILE10_BG_DISABLED_COLOR},
${TILE10_TITLE_ACTIVE_COLOR}, ${TILE10_TITLE_DISABLED_COLOR},
${TILE10_VALUE_ACTIVE_COLOR}, ${TILE10_VALUE_DISABLED_COLOR});
# HA blueprint transport; never publishes the JSON command as an HA state.
text:
- platform: smart_display
id: dashboard_inbox
name: "Tegelinstellingen"
internal: ${DASHBOARD_INBOX_INTERNAL}