6322 lines
283 KiB
YAML
6322 lines
283 KiB
YAML
############################################################
|
||
# SmartDisplay – iOS Home app–inspired Tiles UI
|
||
# Target device: ESP32-2432S028 / Cheap Yellow Display (CYD)
|
||
# Resolution: configurable via ORIENTATION substitutions (landscape 320×240 default)
|
||
#
|
||
# Target hardware
|
||
# - Cheap Yellow Display (ESP32-2432S028 or compatible)
|
||
# - Built-in ILI9341 320x240 display
|
||
# - Built-in XPT2046 touchscreen
|
||
#
|
||
# What this config does
|
||
# - Renders a gradient background + two fixed pages of “tiles” (6 + 4) (LVGL).
|
||
# - Each tile shows: icon (MDI font), title, and a value line.
|
||
# - Tile taps publish a short-lived text_sensor event (smartdisplay_action)
|
||
# so you can build Home Assistant automations on top.
|
||
# - Optional: the device can also call Home Assistant services directly
|
||
# (toggle lights/switches/fans/covers, activate scenes/scripts, set fan preset,
|
||
# set light brightness, set fan percentage, set cover position).
|
||
#
|
||
# The layout is inspired by common modern smart home UIs.
|
||
#
|
||
# How to customize
|
||
# 1) Create device.yaml with tools/new_device.py; edit its TILE1..TILE10 overrides.
|
||
# 2) If you change icons, update the FONT GLYPHS block in the substitutions below.
|
||
# Each glyph must appear exactly once — same icon on multiple tiles is fine, just list it once.
|
||
# 3) The background is a gradient (BG_TOP_COLOR / BG_BOTTOM_COLOR), not an image.
|
||
# Required fonts are bundled in fonts/ (Roboto and Material Design Icons).
|
||
#
|
||
# Tile configuration model
|
||
# - TILE*_TYPE defines the entity kind: light | fan | switch | scene | script | cover
|
||
# - TILE*_TAP_ACTION defines what happens on short tap:
|
||
# auto | toggle | activate | fan_toggle_preset | custom
|
||
# - TILE*_LONGPRESS defines the long press mode: none | slider | action
|
||
# slider: opens a value overlay — configure with TILE*_LONGPRESS_SLIDER + TILE*_LONGPRESS_OFF_VALUE
|
||
# action: fires a different entity — configure with TILE*_LONGPRESS_ACTION*
|
||
# - TILE*_VALUE_MODE defines how the value line is rendered:
|
||
# auto | brightness | percentage | preset | text
|
||
#
|
||
# Notes
|
||
# - DIRECT_ACTIONS="true" enables direct HA service calls from the device.
|
||
# If "false", only the smartdisplay_action event is emitted.
|
||
# - In auto mode, values and long-press behavior are derived from TILE*_TYPE:
|
||
# light -> brightness, fan -> percentage/preset, scene/script -> text
|
||
#
|
||
# Device configuration: home-like-2432s028.yaml (ESP32-2432S028).
|
||
# home-like.yaml targets the separate ST7789V/GT911 board.
|
||
# Credentials stay in secrets.yaml. Fonts are bundled for offline validation.
|
||
# Start at README.md; docs/ covers calibration, HA setup and acceptance tests.
|
||
#
|
||
# What you must adapt before flashing
|
||
# 1) Secrets: api_encryption_key, ota_password, WiFi credentials (see secrets.yaml)
|
||
# 2) Pins, touch calibration and transform if your CYD variant differs
|
||
# 3) TILE*_ENTITY / TILE*_STATE_ENTITY: Home Assistant entity IDs you want to control/mirror
|
||
# 4) Orientation + touch calibration:
|
||
# - rotation is set via LVGL_ROTATION in the ORIENTATION presets (LVGL
|
||
# rotates display + touch together, ESPHome 2026.4+)
|
||
# - calibration is device-specific
|
||
############################################################
|
||
|
||
substitutions:
|
||
DEVICE_NAME: "cyd-new"
|
||
DEVICE_FRIENDLY_NAME: "Mijn CYD"
|
||
CALIBRATION_ON_BOOT: "false"
|
||
TOUCH_AFFINE_XX: "1.000000000"
|
||
TOUCH_AFFINE_XY: "0.000000000"
|
||
TOUCH_AFFINE_XC: "0.000000000"
|
||
TOUCH_AFFINE_YX: "0.000000000"
|
||
TOUCH_AFFINE_YY: "1.000000000"
|
||
TOUCH_AFFINE_YC: "0.000000000"
|
||
|
||
TILE_COUNT: "6" # 1-10; <=6 hides page navigation
|
||
|
||
DIRECT_ACTIONS: "false" # true = call Home Assistant services directly, false = only emit action events
|
||
ROOM_NAME: "Woonkamer" # 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: "0x7B7B6F" # 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: "0xFFFFFF" # tile background color when active
|
||
TILE1_BG_DISABLED_COLOR: "0x939391" # tile background color when inactive
|
||
TILE1_TITLE_ACTIVE_COLOR: "0x000000" # title color when active
|
||
TILE1_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
|
||
TILE1_VALUE_ACTIVE_COLOR: "0x7A7A7C" # value color when active
|
||
TILE1_VALUE_DISABLED_COLOR: "0xD9D9D9" # 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: "0x7B7B6F" # 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: "0xFFFFFF" # tile background color when active
|
||
TILE2_BG_DISABLED_COLOR: "0x939391" # tile background color when inactive
|
||
TILE2_TITLE_ACTIVE_COLOR: "0x000000" # title color when active
|
||
TILE2_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
|
||
TILE2_VALUE_ACTIVE_COLOR: "0x7A7A7C" # value color when active
|
||
TILE2_VALUE_DISABLED_COLOR: "0xD9D9D9" # 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: "0x7B7B6F" # 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: "0xFFFFFF" # tile background color when active
|
||
TILE3_BG_DISABLED_COLOR: "0x939391" # tile background color when inactive
|
||
TILE3_TITLE_ACTIVE_COLOR: "0x000000" # title color when active
|
||
TILE3_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
|
||
TILE3_VALUE_ACTIVE_COLOR: "0x7A7A7C" # value color when active
|
||
TILE3_VALUE_DISABLED_COLOR: "0xD9D9D9" # 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: "0x7B7B6F" # 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: "0xFFFFFF" # tile background color when active
|
||
TILE4_BG_DISABLED_COLOR: "0x939391" # tile background color when inactive
|
||
TILE4_TITLE_ACTIVE_COLOR: "0x000000" # title color when active
|
||
TILE4_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
|
||
TILE4_VALUE_ACTIVE_COLOR: "0x7A7A7C" # value color when active
|
||
TILE4_VALUE_DISABLED_COLOR: "0xD9D9D9" # 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: "0x7B7B6F" # 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: "0xFFFFFF" # tile background color when active
|
||
TILE5_BG_DISABLED_COLOR: "0x939391" # tile background color when inactive
|
||
TILE5_TITLE_ACTIVE_COLOR: "0x000000" # title color when active
|
||
TILE5_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
|
||
TILE5_VALUE_ACTIVE_COLOR: "0x7A7A7C" # value color when active
|
||
TILE5_VALUE_DISABLED_COLOR: "0xD9D9D9" # 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: "0x7B7B6F" # 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: "0xFFFFFF" # tile background color when active
|
||
TILE6_BG_DISABLED_COLOR: "0x939391" # tile background color when inactive
|
||
TILE6_TITLE_ACTIVE_COLOR: "0x000000" # title color when active
|
||
TILE6_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
|
||
TILE6_VALUE_ACTIVE_COLOR: "0x7A7A7C" # value color when active
|
||
TILE6_VALUE_DISABLED_COLOR: "0xD9D9D9" # 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: "0x7B7B6F" # 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: "0xFFFFFF" # tile background color when active
|
||
TILE7_BG_DISABLED_COLOR: "0x939391" # tile background color when inactive
|
||
TILE7_TITLE_ACTIVE_COLOR: "0x000000" # title color when active
|
||
TILE7_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
|
||
TILE7_VALUE_ACTIVE_COLOR: "0x7A7A7C" # value color when active
|
||
TILE7_VALUE_DISABLED_COLOR: "0xD9D9D9" # 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: "0x7B7B6F" # 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: "0xFFFFFF" # tile background color when active
|
||
TILE8_BG_DISABLED_COLOR: "0x939391" # tile background color when inactive
|
||
TILE8_TITLE_ACTIVE_COLOR: "0x000000" # title color when active
|
||
TILE8_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
|
||
TILE8_VALUE_ACTIVE_COLOR: "0x7A7A7C" # value color when active
|
||
TILE8_VALUE_DISABLED_COLOR: "0xD9D9D9" # 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: "0x7B7B6F" # 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: "0xFFFFFF" # tile background color when active
|
||
TILE9_BG_DISABLED_COLOR: "0x939391" # tile background color when inactive
|
||
TILE9_TITLE_ACTIVE_COLOR: "0x000000" # title color when active
|
||
TILE9_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
|
||
TILE9_VALUE_ACTIVE_COLOR: "0x7A7A7C" # value color when active
|
||
TILE9_VALUE_DISABLED_COLOR: "0xD9D9D9" # 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: "0x7B7B6F" # 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: "0xFFFFFF" # tile background color when active
|
||
TILE10_BG_DISABLED_COLOR: "0x939391" # tile background color when inactive
|
||
TILE10_TITLE_ACTIVE_COLOR: "0x000000" # title color when active
|
||
TILE10_TITLE_DISABLED_COLOR: "0xFFFFFF" # title color when inactive
|
||
TILE10_VALUE_ACTIVE_COLOR: "0x7A7A7C" # value color when active
|
||
TILE10_VALUE_DISABLED_COLOR: "0xD9D9D9" # 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)
|
||
|
||
|
||
# ============================================================
|
||
# ORIENTATION / LAYOUT
|
||
# Uncomment ONE preset block below, comment out the others.
|
||
# Touch calibration values (TOUCH_CAL_*) are device-specific —
|
||
# portrait/flipped values are approximate and may need tuning.
|
||
#
|
||
# LVGL_ROTATION (0/90/180/270) drives the rotation — LVGL rotates both the
|
||
# display output and the touch coordinates (ESPHome 2026.4+). DISPLAY_W/H are
|
||
# the *logical* (post-rotation) canvas size used by the tile layout below;
|
||
# the physical panel is always driven at its native 240×320 (see display:).
|
||
#
|
||
# Tile content layout:
|
||
# Landscape — icon left, title + value to its right
|
||
# Portrait — icon centered on top, title + value stacked below
|
||
# ============================================================
|
||
|
||
# ----- 0° — Landscape 320×240 (default, USB on right) -----
|
||
ORIENTATION: "0"
|
||
DISPLAY_W: "320"
|
||
DISPLAY_H: "240"
|
||
LVGL_ROTATION: "90"
|
||
TOUCH_SWAP_XY: "false"
|
||
# true op dit bord (resistieve XPT2046) — was false op het 032c-bord
|
||
# (capacitieve GT911). Dit is de originele upstream-waarde voor dit paneel.
|
||
TOUCH_MIRROR_X: "true"
|
||
TOUCH_MIRROR_Y: "false"
|
||
# Measured on this board: median top-right raw=(523,3390),
|
||
# bottom-left raw=(3487,482), at screen (299,20) and (20,219).
|
||
# Extrapolated to panel edges; verify with the isolated corner test.
|
||
TOUCH_CAL_X_MIN: "280"
|
||
TOUCH_CAL_X_MAX: "3860"
|
||
TOUCH_CAL_Y_MIN: "340"
|
||
TOUCH_CAL_Y_MAX: "3860"
|
||
GRID_COL1_X: "9"
|
||
GRID_COL2_X: "164"
|
||
GRID_ROW1_Y: "0"
|
||
GRID_ROW2_Y: "56"
|
||
GRID_ROW3_Y: "112"
|
||
GRID_ROW4_Y: "168"
|
||
GRID_ROW5_Y: "224"
|
||
SCROLL_Y: "40"
|
||
SCROLL_H: "164"
|
||
TILE_W: "147"
|
||
TILE_H: "52"
|
||
HEADER_Y: "14"
|
||
OVERLAY_SLIDER_W: "96"
|
||
OVERLAY_SLIDER_H: "160"
|
||
OVERLAY_ICON_Y: "74"
|
||
BG_TOP_COLOR: "0xAEC4D9" # achtergrond boven (zacht blauw)
|
||
BG_BOTTOM_COLOR: "0xD6BB9E" # achtergrond onder (warm zand)
|
||
TILE_ICON_X: "0"
|
||
TILE_ICON_Y: "0"
|
||
TILE_TITLE_X: "48"
|
||
TILE_TITLE_Y: "4"
|
||
TILE_VALUE_X: "48"
|
||
TILE_VALUE_Y: "18"
|
||
|
||
# ----- 90° — Portrait 240×320 (USB on bottom) -----
|
||
# ORIENTATION: "90"
|
||
# DISPLAY_W: "240"
|
||
# DISPLAY_H: "320"
|
||
# LVGL_ROTATION: "180"
|
||
# TOUCH_SWAP_XY: "false"
|
||
# TOUCH_MIRROR_X: "true"
|
||
# TOUCH_MIRROR_Y: "false"
|
||
# TOUCH_CAL_X_MIN: "280"
|
||
# TOUCH_CAL_X_MAX: "3860"
|
||
# TOUCH_CAL_Y_MIN: "340"
|
||
# TOUCH_CAL_Y_MAX: "3860"
|
||
# GRID_COL1_X: "6"
|
||
# GRID_COL2_X: "123"
|
||
# GRID_ROW1_Y: "8"
|
||
# GRID_ROW2_Y: "96"
|
||
# GRID_ROW3_Y: "184"
|
||
# GRID_ROW4_Y: "272"
|
||
# GRID_ROW5_Y: "360"
|
||
# SCROLL_Y: "44"
|
||
# SCROLL_H: "276"
|
||
# TILE_W: "111"
|
||
# TILE_H: "80"
|
||
# HEADER_Y: "14"
|
||
# OVERLAY_SLIDER_W: "96"
|
||
# OVERLAY_SLIDER_H: "200"
|
||
# OVERLAY_ICON_Y: "94"
|
||
# BG_TOP_COLOR: "0xAEC4D9" # achtergrond boven (zacht blauw)
|
||
# BG_BOTTOM_COLOR: "0xD6BB9E" # achtergrond onder (warm zand)
|
||
# TILE_ICON_X: "0"
|
||
# TILE_ICON_Y: "0"
|
||
# TILE_TITLE_X: "4"
|
||
# TILE_TITLE_Y: "38"
|
||
# TILE_VALUE_X: "4"
|
||
# TILE_VALUE_Y: "52"
|
||
|
||
# ----- 180° — Landscape flipped 320×240 (USB on left) -----
|
||
# ORIENTATION: "180"
|
||
# DISPLAY_W: "320"
|
||
# DISPLAY_H: "240"
|
||
# LVGL_ROTATION: "270"
|
||
# TOUCH_SWAP_XY: "false"
|
||
# TOUCH_MIRROR_X: "true"
|
||
# TOUCH_MIRROR_Y: "false"
|
||
# TOUCH_CAL_X_MIN: "280"
|
||
# TOUCH_CAL_X_MAX: "3860"
|
||
# TOUCH_CAL_Y_MIN: "340"
|
||
# TOUCH_CAL_Y_MAX: "3860"
|
||
# GRID_COL1_X: "9"
|
||
# GRID_COL2_X: "164"
|
||
# GRID_ROW1_Y: "8"
|
||
# GRID_ROW2_Y: "68"
|
||
# GRID_ROW3_Y: "128"
|
||
# GRID_ROW4_Y: "188"
|
||
# GRID_ROW5_Y: "248"
|
||
# SCROLL_Y: "44"
|
||
# SCROLL_H: "196"
|
||
# TILE_W: "147"
|
||
# TILE_H: "52"
|
||
# HEADER_Y: "14"
|
||
# OVERLAY_SLIDER_W: "96"
|
||
# OVERLAY_SLIDER_H: "160"
|
||
# OVERLAY_ICON_Y: "74"
|
||
# BG_TOP_COLOR: "0xAEC4D9" # achtergrond boven (zacht blauw)
|
||
# BG_BOTTOM_COLOR: "0xD6BB9E" # achtergrond onder (warm zand)
|
||
# TILE_ICON_X: "0"
|
||
# TILE_ICON_Y: "0"
|
||
# TILE_TITLE_X: "48"
|
||
# TILE_TITLE_Y: "4"
|
||
# TILE_VALUE_X: "48"
|
||
# TILE_VALUE_Y: "18"
|
||
|
||
# ----- 270° — Portrait flipped 240×320 (USB on top) -----
|
||
# ORIENTATION: "270"
|
||
# DISPLAY_W: "240"
|
||
# DISPLAY_H: "320"
|
||
# LVGL_ROTATION: "0"
|
||
# TOUCH_SWAP_XY: "false"
|
||
# TOUCH_MIRROR_X: "true"
|
||
# TOUCH_MIRROR_Y: "false"
|
||
# TOUCH_CAL_X_MIN: "280"
|
||
# TOUCH_CAL_X_MAX: "3860"
|
||
# TOUCH_CAL_Y_MIN: "340"
|
||
# TOUCH_CAL_Y_MAX: "3860"
|
||
# GRID_COL1_X: "6"
|
||
# GRID_COL2_X: "123"
|
||
# GRID_ROW1_Y: "8"
|
||
# GRID_ROW2_Y: "96"
|
||
# GRID_ROW3_Y: "184"
|
||
# GRID_ROW4_Y: "272"
|
||
# GRID_ROW5_Y: "360"
|
||
# SCROLL_Y: "44"
|
||
# SCROLL_H: "276"
|
||
# TILE_W: "111"
|
||
# TILE_H: "80"
|
||
# HEADER_Y: "14"
|
||
# OVERLAY_SLIDER_W: "96"
|
||
# OVERLAY_SLIDER_H: "200"
|
||
# OVERLAY_ICON_Y: "94"
|
||
# BG_TOP_COLOR: "0xAEC4D9" # achtergrond boven (zacht blauw)
|
||
# BG_BOTTOM_COLOR: "0xD6BB9E" # achtergrond onder (warm zand)
|
||
# TILE_ICON_X: "0"
|
||
# TILE_ICON_Y: "0"
|
||
# TILE_TITLE_X: "4"
|
||
# TILE_TITLE_Y: "38"
|
||
# TILE_VALUE_X: "4"
|
||
# TILE_VALUE_Y: "52"
|
||
|
||
# ============================================================
|
||
# 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: local
|
||
path: components
|
||
components: [xpt2046]
|
||
|
||
esphome:
|
||
includes:
|
||
- cyd_ui.h
|
||
name: ${DEVICE_NAME}
|
||
friendly_name: ${DEVICE_FRIENDLY_NAME}
|
||
on_boot:
|
||
priority: -100
|
||
then:
|
||
- lambda: |-
|
||
// Per-panel affine correction; shared defaults are neutral.
|
||
id(ts_touch).set_raw_correction(${TOUCH_AFFINE_XX}f, ${TOUCH_AFFINE_XY}f, ${TOUCH_AFFINE_XC}f, ${TOUCH_AFFINE_YX}f, ${TOUCH_AFFINE_YY}f, ${TOUCH_AFFINE_YC}f);
|
||
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);
|
||
- 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", "USB calibration ready; no tile actions");
|
||
|
||
|
||
esp32:
|
||
board: esp32dev
|
||
framework:
|
||
type: esp-idf
|
||
advanced:
|
||
# ESPHome adviseert dit zelf in de boot-log. Levert ~40KB extra IRAM
|
||
# en een kleinere binary op, en op dit bord telt elke KB.
|
||
sram1_as_iram: true
|
||
# minimum_chip_revision bewust weggelaten: dat stond hier voor het
|
||
# 032c-bord (rev 3.1 bevestigd), maar dit is een ander fysiek bordje
|
||
# met een onbekende chip-revisie. Met een onterecht hoge minimum
|
||
# weigert de firmware simpelweg te booten.
|
||
version: recommended
|
||
variant: esp32
|
||
|
||
logger:
|
||
# 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: 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: ui_self_test
|
||
then:
|
||
- script.execute: ui_self_test
|
||
encryption:
|
||
key: !secret api_encryption_key
|
||
|
||
ota:
|
||
- platform: esphome
|
||
password: !secret ota_password
|
||
|
||
wifi:
|
||
ssid: !secret wifi_ssid
|
||
password: !secret wifi_password
|
||
ap:
|
||
ssid: "Smartdisplay Fallback Hotspot"
|
||
password: !secret ap_password
|
||
|
||
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: dit resistieve XPT2046-paneel geeft soms
|
||
# 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: "fonts/Roboto-500.ttf"
|
||
id: time_label
|
||
size: 18
|
||
bpp: 4
|
||
glyphs: ['0','1','2','3','4','5','6','7','8','9',':',' ', 'A', 'M', 'P']
|
||
|
||
- file: "fonts/Roboto-500.ttf"
|
||
id: headline
|
||
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: "fonts/Roboto-700.ttf"
|
||
id: label
|
||
size: 11
|
||
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: "fonts/Roboto-400.ttf"
|
||
id: sublabel
|
||
size: 11
|
||
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: "fonts/Roboto-400.ttf"
|
||
id: sublabel_big
|
||
size: 14
|
||
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: 'fonts/materialdesignicons-webfont.ttf'
|
||
id: materialdesign_icons
|
||
size: 28
|
||
glyphs:
|
||
- "${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: homeassistant
|
||
id: tile1_brightness
|
||
entity_id: ${TILE1_ENTITY}
|
||
attribute: brightness
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile1_percentage
|
||
entity_id: ${TILE1_ENTITY}
|
||
attribute: percentage
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile2_brightness
|
||
entity_id: ${TILE2_ENTITY}
|
||
attribute: brightness
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile2_percentage
|
||
entity_id: ${TILE2_ENTITY}
|
||
attribute: percentage
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile3_brightness
|
||
entity_id: ${TILE3_ENTITY}
|
||
attribute: brightness
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile3_percentage
|
||
entity_id: ${TILE3_ENTITY}
|
||
attribute: percentage
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile4_brightness
|
||
entity_id: ${TILE4_ENTITY}
|
||
attribute: brightness
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile4_percentage
|
||
entity_id: ${TILE4_ENTITY}
|
||
attribute: percentage
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile5_brightness
|
||
entity_id: ${TILE5_ENTITY}
|
||
attribute: brightness
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile5_percentage
|
||
entity_id: ${TILE5_ENTITY}
|
||
attribute: percentage
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile6_brightness
|
||
entity_id: ${TILE6_ENTITY}
|
||
attribute: brightness
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile6_percentage
|
||
entity_id: ${TILE6_ENTITY}
|
||
attribute: 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: homeassistant
|
||
id: tile7_brightness
|
||
entity_id: ${TILE7_ENTITY}
|
||
attribute: 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: homeassistant
|
||
id: tile9_brightness
|
||
entity_id: ${TILE9_ENTITY}
|
||
attribute: brightness
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile1_cover_position
|
||
entity_id: ${TILE1_ENTITY}
|
||
attribute: current_position
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile2_cover_position
|
||
entity_id: ${TILE2_ENTITY}
|
||
attribute: current_position
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile3_cover_position
|
||
entity_id: ${TILE3_ENTITY}
|
||
attribute: current_position
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile4_cover_position
|
||
entity_id: ${TILE4_ENTITY}
|
||
attribute: current_position
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile5_cover_position
|
||
entity_id: ${TILE5_ENTITY}
|
||
attribute: current_position
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile6_cover_position
|
||
entity_id: ${TILE6_ENTITY}
|
||
attribute: current_position
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile1_climate_current_temperature
|
||
entity_id: ${TILE1_ENTITY}
|
||
attribute: ${TILE1_CLIMATE_CURRENT_TEMP_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile1_climate_target_temperature
|
||
entity_id: ${TILE1_ENTITY}
|
||
attribute: ${TILE1_CLIMATE_TARGET_TEMP_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile1_climate_humidity
|
||
entity_id: ${TILE1_ENTITY}
|
||
attribute: ${TILE1_CLIMATE_HUMIDITY_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile2_climate_current_temperature
|
||
entity_id: ${TILE2_ENTITY}
|
||
attribute: ${TILE2_CLIMATE_CURRENT_TEMP_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile2_climate_target_temperature
|
||
entity_id: ${TILE2_ENTITY}
|
||
attribute: ${TILE2_CLIMATE_TARGET_TEMP_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile2_climate_humidity
|
||
entity_id: ${TILE2_ENTITY}
|
||
attribute: ${TILE2_CLIMATE_HUMIDITY_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile3_climate_current_temperature
|
||
entity_id: ${TILE3_ENTITY}
|
||
attribute: ${TILE3_CLIMATE_CURRENT_TEMP_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile3_climate_target_temperature
|
||
entity_id: ${TILE3_ENTITY}
|
||
attribute: ${TILE3_CLIMATE_TARGET_TEMP_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile3_climate_humidity
|
||
entity_id: ${TILE3_ENTITY}
|
||
attribute: ${TILE3_CLIMATE_HUMIDITY_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile4_climate_current_temperature
|
||
entity_id: ${TILE4_ENTITY}
|
||
attribute: ${TILE4_CLIMATE_CURRENT_TEMP_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile4_climate_target_temperature
|
||
entity_id: ${TILE4_ENTITY}
|
||
attribute: ${TILE4_CLIMATE_TARGET_TEMP_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile4_climate_humidity
|
||
entity_id: ${TILE4_ENTITY}
|
||
attribute: ${TILE4_CLIMATE_HUMIDITY_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile5_climate_current_temperature
|
||
entity_id: ${TILE5_ENTITY}
|
||
attribute: ${TILE5_CLIMATE_CURRENT_TEMP_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile5_climate_target_temperature
|
||
entity_id: ${TILE5_ENTITY}
|
||
attribute: ${TILE5_CLIMATE_TARGET_TEMP_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile5_climate_humidity
|
||
entity_id: ${TILE5_ENTITY}
|
||
attribute: ${TILE5_CLIMATE_HUMIDITY_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile6_climate_current_temperature
|
||
entity_id: ${TILE6_ENTITY}
|
||
attribute: ${TILE6_CLIMATE_CURRENT_TEMP_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile6_climate_target_temperature
|
||
entity_id: ${TILE6_ENTITY}
|
||
attribute: ${TILE6_CLIMATE_TARGET_TEMP_ATTRIBUTE}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile6_climate_humidity
|
||
entity_id: ${TILE6_ENTITY}
|
||
attribute: ${TILE6_CLIMATE_HUMIDITY_ATTRIBUTE}
|
||
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
|
||
id: smartdisplay_action
|
||
name: "SmartDisplay Action"
|
||
icon: mdi:gesture-tap-button
|
||
|
||
- platform: homeassistant
|
||
id: ha_state_tile1
|
||
entity_id: ${TILE1_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: ha_state_tile2
|
||
entity_id: ${TILE2_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: ha_state_tile3
|
||
entity_id: ${TILE3_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: ha_state_tile4
|
||
entity_id: ${TILE4_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: ha_state_tile5
|
||
entity_id: ${TILE5_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: ha_state_tile6
|
||
entity_id: ${TILE6_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: ha_state_tile7
|
||
entity_id: ${TILE7_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: ha_state_tile8
|
||
entity_id: ${TILE8_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: ha_state_tile9
|
||
entity_id: ${TILE9_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: ha_state_tile10
|
||
entity_id: ${TILE10_STATE_ENTITY}
|
||
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: homeassistant
|
||
id: tile9_supported_color_modes
|
||
entity_id: ${TILE9_ENTITY}
|
||
attribute: 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: homeassistant
|
||
id: tile7_supported_color_modes
|
||
entity_id: ${TILE7_ENTITY}
|
||
attribute: supported_color_modes
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile1_preset
|
||
entity_id: ${TILE1_ENTITY}
|
||
attribute: preset_mode
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile2_preset
|
||
entity_id: ${TILE2_ENTITY}
|
||
attribute: preset_mode
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile3_preset
|
||
entity_id: ${TILE3_ENTITY}
|
||
attribute: preset_mode
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile4_preset
|
||
entity_id: ${TILE4_ENTITY}
|
||
attribute: preset_mode
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile5_preset
|
||
entity_id: ${TILE5_ENTITY}
|
||
attribute: preset_mode
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile6_preset
|
||
entity_id: ${TILE6_ENTITY}
|
||
attribute: preset_mode
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile1_supported_color_modes
|
||
entity_id: ${TILE1_ENTITY}
|
||
attribute: supported_color_modes
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile2_supported_color_modes
|
||
entity_id: ${TILE2_ENTITY}
|
||
attribute: supported_color_modes
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile3_supported_color_modes
|
||
entity_id: ${TILE3_ENTITY}
|
||
attribute: supported_color_modes
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile4_supported_color_modes
|
||
entity_id: ${TILE4_ENTITY}
|
||
attribute: supported_color_modes
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile5_supported_color_modes
|
||
entity_id: ${TILE5_ENTITY}
|
||
attribute: supported_color_modes
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile6_supported_color_modes
|
||
entity_id: ${TILE6_ENTITY}
|
||
attribute: supported_color_modes
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile1_climate_mode
|
||
entity_id: ${TILE1_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile2_climate_mode
|
||
entity_id: ${TILE2_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile3_climate_mode
|
||
entity_id: ${TILE3_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile4_climate_mode
|
||
entity_id: ${TILE4_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile5_climate_mode
|
||
entity_id: ${TILE5_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile6_climate_mode
|
||
entity_id: ${TILE6_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile1_climate_supported_modes
|
||
entity_id: ${TILE1_ENTITY}
|
||
attribute: hvac_modes
|
||
|
||
- platform: homeassistant
|
||
id: tile2_climate_supported_modes
|
||
entity_id: ${TILE2_ENTITY}
|
||
attribute: hvac_modes
|
||
|
||
- platform: homeassistant
|
||
id: tile3_climate_supported_modes
|
||
entity_id: ${TILE3_ENTITY}
|
||
attribute: hvac_modes
|
||
|
||
- platform: homeassistant
|
||
id: tile4_climate_supported_modes
|
||
entity_id: ${TILE4_ENTITY}
|
||
attribute: hvac_modes
|
||
|
||
- platform: homeassistant
|
||
id: tile5_climate_supported_modes
|
||
entity_id: ${TILE5_ENTITY}
|
||
attribute: hvac_modes
|
||
|
||
- platform: homeassistant
|
||
id: tile6_climate_supported_modes
|
||
entity_id: ${TILE6_ENTITY}
|
||
attribute: hvac_modes
|
||
|
||
- platform: homeassistant
|
||
id: tile1_cover_state
|
||
entity_id: ${TILE1_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile2_cover_state
|
||
entity_id: ${TILE2_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile3_cover_state
|
||
entity_id: ${TILE3_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile4_cover_state
|
||
entity_id: ${TILE4_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile5_cover_state
|
||
entity_id: ${TILE5_STATE_ENTITY}
|
||
on_value: { then: [ lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();' ] }
|
||
|
||
- platform: homeassistant
|
||
id: tile6_cover_state
|
||
entity_id: ${TILE6_STATE_ENTITY}
|
||
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
|
||
# Twee SPI bussen op de 2432S028: een voor het display, een losse voor de
|
||
# resistieve XPT2046 touch (geen I2C op dit bord, in tegenstelling tot 032c).
|
||
spi:
|
||
- id: lcd
|
||
clk_pin: GPIO14
|
||
mosi_pin: GPIO13
|
||
|
||
- id: my_touchscreen
|
||
clk_pin: GPIO25
|
||
mosi_pin: GPIO32
|
||
miso_pin: GPIO39
|
||
|
||
output:
|
||
- platform: ledc
|
||
pin: GPIO21
|
||
id: gpio_backlight_pwm
|
||
# ESPHome's ledc default is 1kHz. Bij stilstand zie je dat niet (ruim
|
||
# boven wat het oog als flikkering waarneemt), maar bij beweging van het
|
||
# scherm onthult motion-blur de aan/uit-pulsen als strepen (zelfde effect
|
||
# als een PWM-LED die "schokt" als je hem beweegt). 20kHz zit ruim boven
|
||
# de snelheid waarbij beweging dat nog zichtbaar maakt, en laat nog altijd
|
||
# ruim voldoende duty-cycle resolutie over voor vloeiend dimmen.
|
||
frequency: 20000Hz
|
||
|
||
- platform: ledc
|
||
id: output_red
|
||
pin: GPIO4
|
||
inverted: true
|
||
- platform: ledc
|
||
id: output_green
|
||
pin: GPIO16
|
||
inverted: true
|
||
- platform: ledc
|
||
id: output_blue
|
||
pin: GPIO17
|
||
inverted: true
|
||
|
||
light:
|
||
- platform: monochromatic
|
||
output: gpio_backlight_pwm
|
||
name: "Power Display Backlight"
|
||
id: back_light
|
||
restore_mode: ALWAYS_ON
|
||
|
||
- platform: rgb
|
||
name: LED
|
||
red: output_red
|
||
id: led
|
||
green: output_green
|
||
blue: output_blue
|
||
restore_mode: ALWAYS_OFF
|
||
|
||
# Touchscreen configuration (XPT2046, resistief, via SPI)
|
||
# XPT2046 calibration is board-specific. Poll at 50Hz; LVGL rotates touch
|
||
# with the display. GPIO36 IRQ is deliberately unused (driver requests a
|
||
# pull-up that this input-only pin cannot provide).
|
||
touchscreen:
|
||
platform: xpt2046
|
||
id: ts_touch
|
||
spi_id: my_touchscreen
|
||
cs_pin: 33
|
||
# Polling avoids GPIO36 pull-up errors and noisy PENIRQ edges.
|
||
update_interval: 20ms
|
||
# Diagnosis caught a false corner contact at pressure 422. Deliberate
|
||
# contacts measured 936-2643; reject the low-pressure tail on release.
|
||
threshold: 800
|
||
|
||
calibration:
|
||
x_min: ${TOUCH_CAL_X_MIN}
|
||
x_max: ${TOUCH_CAL_X_MAX}
|
||
y_min: ${TOUCH_CAL_Y_MIN}
|
||
y_max: ${TOUCH_CAL_Y_MAX}
|
||
|
||
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", "press native=%u,%u raw=%d,%d pressure=%d calibration=%d", touch.x, touch.y, id(ts_touch).filtered_raw_x(), id(ts_touch).filtered_raw_y(), touch.z_raw, 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 configuration (ILI9341)
|
||
display:
|
||
- platform: mipi_spi
|
||
id: my_display
|
||
spi_id: lcd
|
||
# Standard single-Micro-USB CYD; 40MHz matches ESPHome's board preset.
|
||
model: ILI9341
|
||
data_rate: 40MHz
|
||
cs_pin: 15
|
||
dc_pin: 2
|
||
invert_colors: false
|
||
update_interval: never
|
||
auto_clear_enabled: false
|
||
|
||
# Native panel dimensions (portrait). Do NOT swap these per orientation and
|
||
# do NOT add a display transform — rotation is handled by LVGL below.
|
||
# ESPHome 2026.6+ rejects a swapped transform here with "Invalid offsets".
|
||
dimensions:
|
||
width: 240
|
||
height: 320
|
||
|
||
lvgl:
|
||
on_draw_start:
|
||
- lambda: 'id(rendered_frames)++;'
|
||
displays:
|
||
- my_display
|
||
touchscreens:
|
||
- touchscreen_id: ts_touch
|
||
|
||
# De ESP32-WROOM-32 heeft geen PSRAM; een volledige framebuffer (320x240x2
|
||
# = 153600 bytes) past niet in DRAM. 25% is de gebruikelijke waarde.
|
||
# 12% in plaats van 25%: laat ~20KB extra aaneengesloten geheugen vrij,
|
||
# en LVGL schaalt de stroken van een ARGB-laag mee met de beschikbare
|
||
# tekenruimte, dus ook de gevraagde allocaties worden kleiner.
|
||
buffer_size: 12%
|
||
|
||
# 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: 18
|
||
border_width: 0
|
||
pad_left: 8
|
||
pad_right: 8
|
||
pad_top: 8
|
||
pad_bottom: 8
|
||
shadow_width: 0
|
||
shadow_opa: 40%
|
||
|
||
- id: style_tile_disabled
|
||
bg_color: 0x939391
|
||
bg_opa: COVER
|
||
radius: 18
|
||
border_width: 0
|
||
pad_left: 8
|
||
pad_right: 8
|
||
pad_top: 8
|
||
pad_bottom: 8
|
||
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: 11
|
||
y: ${HEADER_Y}
|
||
text: "${ROOM_NAME}"
|
||
styles: style_room
|
||
|
||
- label:
|
||
id: lbl_time
|
||
align: TOP_RIGHT
|
||
x: -11
|
||
y: ${HEADER_Y}
|
||
text: ""
|
||
styles: style_time
|
||
|
||
# Six tiles on page 1, four on page 2. No dragging on resistive glass.
|
||
- 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: 36
|
||
height: 36
|
||
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
|
||
x: ${TILE_TITLE_X}
|
||
y: ${TILE_TITLE_Y}
|
||
text: "${TILE1_TITLE}"
|
||
styles: style_title
|
||
clickable: false
|
||
- label:
|
||
id: t1_value
|
||
x: ${TILE_VALUE_X}
|
||
y: ${TILE_VALUE_Y}
|
||
text: "—"
|
||
styles: style_value
|
||
clickable: false
|
||
on_short_click:
|
||
- if:
|
||
condition:
|
||
lambda: 'return 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 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: 36
|
||
height: 36
|
||
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
|
||
x: ${TILE_TITLE_X}
|
||
y: ${TILE_TITLE_Y}
|
||
text: "${TILE2_TITLE}"
|
||
styles: style_title
|
||
clickable: false
|
||
- label:
|
||
id: t2_value
|
||
x: ${TILE_VALUE_X}
|
||
y: ${TILE_VALUE_Y}
|
||
text: "—"
|
||
styles: style_value
|
||
clickable: false
|
||
on_short_click:
|
||
- if:
|
||
condition:
|
||
lambda: 'return 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 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: 36
|
||
height: 36
|
||
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
|
||
x: ${TILE_TITLE_X}
|
||
y: ${TILE_TITLE_Y}
|
||
text: "${TILE3_TITLE}"
|
||
styles: style_title
|
||
clickable: false
|
||
- label:
|
||
id: t3_value
|
||
x: ${TILE_VALUE_X}
|
||
y: ${TILE_VALUE_Y}
|
||
text: "—"
|
||
styles: style_value
|
||
clickable: false
|
||
on_short_click:
|
||
- if:
|
||
condition:
|
||
lambda: 'return 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 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: 36
|
||
height: 36
|
||
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
|
||
x: ${TILE_TITLE_X}
|
||
y: ${TILE_TITLE_Y}
|
||
text: "${TILE4_TITLE}"
|
||
styles: style_title
|
||
clickable: false
|
||
- label:
|
||
id: t4_value
|
||
x: ${TILE_VALUE_X}
|
||
y: ${TILE_VALUE_Y}
|
||
text: "—"
|
||
styles: style_value
|
||
clickable: false
|
||
on_short_click:
|
||
- if:
|
||
condition:
|
||
lambda: 'return 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 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: 36
|
||
height: 36
|
||
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
|
||
x: ${TILE_TITLE_X}
|
||
y: ${TILE_TITLE_Y}
|
||
text: "${TILE5_TITLE}"
|
||
styles: style_title
|
||
clickable: false
|
||
- label:
|
||
id: t5_value
|
||
x: ${TILE_VALUE_X}
|
||
y: ${TILE_VALUE_Y}
|
||
text: "—"
|
||
styles: style_value
|
||
clickable: false
|
||
on_short_click:
|
||
- if:
|
||
condition:
|
||
lambda: 'return 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 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: 36
|
||
height: 36
|
||
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
|
||
x: ${TILE_TITLE_X}
|
||
y: ${TILE_TITLE_Y}
|
||
text: "${TILE6_TITLE}"
|
||
styles: style_title
|
||
clickable: false
|
||
- label:
|
||
id: t6_value
|
||
x: ${TILE_VALUE_X}
|
||
y: ${TILE_VALUE_Y}
|
||
text: "—"
|
||
styles: style_value
|
||
clickable: false
|
||
on_short_click:
|
||
- if:
|
||
condition:
|
||
lambda: 'return 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 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: 36
|
||
height: 36
|
||
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
|
||
x: ${TILE_TITLE_X}
|
||
y: ${TILE_TITLE_Y}
|
||
text: "${TILE7_TITLE}"
|
||
styles: style_title
|
||
clickable: false
|
||
- label:
|
||
id: t7_value
|
||
x: ${TILE_VALUE_X}
|
||
y: ${TILE_VALUE_Y}
|
||
text: "—"
|
||
styles: style_value
|
||
clickable: false
|
||
on_short_click:
|
||
- if:
|
||
condition:
|
||
lambda: 'return 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 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: 36
|
||
height: 36
|
||
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
|
||
x: ${TILE_TITLE_X}
|
||
y: ${TILE_TITLE_Y}
|
||
text: "${TILE8_TITLE}"
|
||
styles: style_title
|
||
clickable: false
|
||
- label:
|
||
id: t8_value
|
||
x: ${TILE_VALUE_X}
|
||
y: ${TILE_VALUE_Y}
|
||
text: "—"
|
||
styles: style_value
|
||
clickable: false
|
||
on_short_click:
|
||
- if:
|
||
condition:
|
||
lambda: 'return 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 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: 36
|
||
height: 36
|
||
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
|
||
x: ${TILE_TITLE_X}
|
||
y: ${TILE_TITLE_Y}
|
||
text: "${TILE9_TITLE}"
|
||
styles: style_title
|
||
clickable: false
|
||
- label:
|
||
id: t9_value
|
||
x: ${TILE_VALUE_X}
|
||
y: ${TILE_VALUE_Y}
|
||
text: "—"
|
||
styles: style_value
|
||
clickable: false
|
||
on_short_click:
|
||
- if:
|
||
condition:
|
||
lambda: 'return 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 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: 36
|
||
height: 36
|
||
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
|
||
x: ${TILE_TITLE_X}
|
||
y: ${TILE_TITLE_Y}
|
||
text: "${TILE10_TITLE}"
|
||
styles: style_title
|
||
clickable: false
|
||
- label:
|
||
id: t10_value
|
||
x: ${TILE_VALUE_X}
|
||
y: ${TILE_VALUE_Y}
|
||
text: "—"
|
||
styles: style_value
|
||
clickable: false
|
||
on_short_click:
|
||
- if:
|
||
condition:
|
||
lambda: 'return 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 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: 9
|
||
y: -2
|
||
width: 100
|
||
height: 32
|
||
pad_all: 0
|
||
radius: 8
|
||
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: -9
|
||
text: "1 / 2"
|
||
text_font: sublabel
|
||
text_color: 0xFFFFFF
|
||
- obj:
|
||
id: page_next
|
||
align: BOTTOM_RIGHT
|
||
x: -9
|
||
y: -2
|
||
width: 100
|
||
height: 32
|
||
pad_all: 0
|
||
radius: 8
|
||
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: 10
|
||
text: "Light"
|
||
styles: style_room
|
||
|
||
- label:
|
||
id: overlay_value
|
||
align: TOP_MID
|
||
y: 35
|
||
text: "100 %"
|
||
styles: style_value_big
|
||
|
||
- slider:
|
||
id: overlay_slider
|
||
align: CENTER
|
||
y: 18
|
||
width: ${OVERLAY_SLIDER_W}
|
||
height: ${OVERLAY_SLIDER_H}
|
||
min_value: 0
|
||
max_value: 100
|
||
value: 100
|
||
adv_hittest: false
|
||
|
||
bg_color: 0x939391
|
||
bg_opa: 100%
|
||
radius: 28
|
||
# 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: 28
|
||
|
||
knob:
|
||
bg_opa: 0%
|
||
border_width: 0
|
||
shadow_width: 0
|
||
pad_all: 10
|
||
|
||
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: -13
|
||
y: -13
|
||
width: 44
|
||
height: 44
|
||
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: 40
|
||
height: 40
|
||
start_angle: 0
|
||
end_angle: 45
|
||
min_value: 0
|
||
max_value: 100
|
||
value: 0
|
||
adjustable: false
|
||
arc_width: 10
|
||
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: 40
|
||
height: 40
|
||
start_angle: 45
|
||
end_angle: 90
|
||
min_value: 0
|
||
max_value: 100
|
||
value: 0
|
||
adjustable: false
|
||
arc_width: 10
|
||
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: 40
|
||
height: 40
|
||
start_angle: 90
|
||
end_angle: 135
|
||
min_value: 0
|
||
max_value: 100
|
||
value: 0
|
||
adjustable: false
|
||
arc_width: 10
|
||
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: 40
|
||
height: 40
|
||
start_angle: 135
|
||
end_angle: 180
|
||
min_value: 0
|
||
max_value: 100
|
||
value: 0
|
||
adjustable: false
|
||
arc_width: 10
|
||
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: 40
|
||
height: 40
|
||
start_angle: 180
|
||
end_angle: 225
|
||
min_value: 0
|
||
max_value: 100
|
||
value: 0
|
||
adjustable: false
|
||
arc_width: 10
|
||
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: 40
|
||
height: 40
|
||
start_angle: 225
|
||
end_angle: 270
|
||
min_value: 0
|
||
max_value: 100
|
||
value: 0
|
||
adjustable: false
|
||
arc_width: 10
|
||
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: 40
|
||
height: 40
|
||
start_angle: 270
|
||
end_angle: 315
|
||
min_value: 0
|
||
max_value: 100
|
||
value: 0
|
||
adjustable: false
|
||
arc_width: 10
|
||
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: 40
|
||
height: 40
|
||
start_angle: 315
|
||
end_angle: 360
|
||
min_value: 0
|
||
max_value: 100
|
||
value: 0
|
||
adjustable: false
|
||
arc_width: 10
|
||
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: 10
|
||
y: 9
|
||
width: 36
|
||
height: 36
|
||
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
|
||
- lvgl.widget.show: brightness_overlay
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "x"
|
||
text_font: sublabel_big
|
||
text_color: 0xFFFFFF
|
||
clickable: false
|
||
|
||
- label:
|
||
id: color_detail_title
|
||
align: TOP_MID
|
||
y: 16
|
||
text: "Light Color"
|
||
styles: style_room
|
||
|
||
- obj:
|
||
id: color_done_button
|
||
align: TOP_RIGHT
|
||
x: -10
|
||
y: 9
|
||
width: 36
|
||
height: 36
|
||
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
|
||
|
||
- obj:
|
||
id: color_mode_tabs
|
||
align: TOP_MID
|
||
y: 58
|
||
width: 190
|
||
height: 28
|
||
radius: 14
|
||
bg_color: 0x3C3C40
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
scrollable: false
|
||
pad_left: 0
|
||
pad_right: 0
|
||
pad_top: 0
|
||
pad_bottom: 0
|
||
widgets:
|
||
- obj:
|
||
id: color_tab_indicator
|
||
x: 2
|
||
y: 2
|
||
width: 93
|
||
height: 24
|
||
radius: 12
|
||
bg_color: 0x8E8E93
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: false
|
||
scrollable: false
|
||
- obj:
|
||
x: 0
|
||
y: 0
|
||
width: 95
|
||
height: 28
|
||
bg_opa: TRANSP
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
pad_left: 0
|
||
pad_right: 0
|
||
pad_top: 0
|
||
pad_bottom: 0
|
||
on_click:
|
||
- script.execute:
|
||
id: show_color_detail_tab
|
||
tab: 0
|
||
widgets:
|
||
- label:
|
||
id: color_tab_label
|
||
align: CENTER
|
||
text: "Color"
|
||
text_font: sublabel_big
|
||
text_color: 0xFFFFFF
|
||
clickable: false
|
||
- obj:
|
||
x: 95
|
||
y: 0
|
||
width: 95
|
||
height: 28
|
||
bg_opa: TRANSP
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
pad_left: 0
|
||
pad_right: 0
|
||
pad_top: 0
|
||
pad_bottom: 0
|
||
on_click:
|
||
- script.execute:
|
||
id: show_color_detail_tab
|
||
tab: 1
|
||
widgets:
|
||
- label:
|
||
id: temp_tab_label
|
||
align: CENTER
|
||
text: "Temp."
|
||
text_font: sublabel_big
|
||
text_color: 0xFFFFFF
|
||
clickable: false
|
||
|
||
- obj:
|
||
id: color_palette_card
|
||
align: TOP_MID
|
||
y: 98
|
||
width: 286
|
||
height: 48
|
||
bg_opa: TRANSP
|
||
border_width: 0
|
||
scrollable: false
|
||
pad_left: 0
|
||
pad_right: 0
|
||
pad_top: 0
|
||
pad_bottom: 0
|
||
widgets:
|
||
- obj:
|
||
id: color_preset_red
|
||
x: 14
|
||
y: 8
|
||
width: 30
|
||
height: 30
|
||
radius: 999
|
||
bg_color: 0xFF3B30
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
on_click:
|
||
- script.execute: { id: set_light_color_name, color: "red" }
|
||
- obj:
|
||
id: color_preset_orange
|
||
x: 61
|
||
y: 8
|
||
width: 30
|
||
height: 30
|
||
radius: 999
|
||
bg_color: 0xFF9F0A
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
on_click:
|
||
- script.execute: { id: set_light_color_name, color: "orange" }
|
||
- obj:
|
||
id: color_preset_yellow
|
||
x: 108
|
||
y: 8
|
||
width: 30
|
||
height: 30
|
||
radius: 999
|
||
bg_color: 0xFFD60A
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
on_click:
|
||
- script.execute: { id: set_light_color_name, color: "yellow" }
|
||
- obj:
|
||
id: color_preset_green
|
||
x: 155
|
||
y: 8
|
||
width: 30
|
||
height: 30
|
||
radius: 999
|
||
bg_color: 0x30D158
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
on_click:
|
||
- script.execute: { id: set_light_color_name, color: "green" }
|
||
- obj:
|
||
id: color_preset_blue
|
||
x: 202
|
||
y: 8
|
||
width: 30
|
||
height: 30
|
||
radius: 999
|
||
bg_color: 0x0A84FF
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
on_click:
|
||
- script.execute: { id: set_light_color_name, color: "blue" }
|
||
- obj:
|
||
id: color_preset_purple
|
||
x: 249
|
||
y: 8
|
||
width: 30
|
||
height: 30
|
||
radius: 999
|
||
bg_color: 0xBF5AF2
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
on_click:
|
||
- script.execute: { id: set_light_color_name, color: "purple" }
|
||
|
||
- obj:
|
||
id: temp_palette_card
|
||
align: TOP_MID
|
||
y: 98
|
||
width: 286
|
||
height: 48
|
||
bg_opa: TRANSP
|
||
border_width: 0
|
||
scrollable: false
|
||
hidden: true
|
||
pad_left: 0
|
||
pad_right: 0
|
||
pad_top: 0
|
||
pad_bottom: 0
|
||
widgets:
|
||
- obj:
|
||
id: temp_preset_2700
|
||
x: 24
|
||
y: 8
|
||
width: 62
|
||
height: 30
|
||
radius: 15
|
||
bg_color: 0xFFD4A3
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
pad_left: 0
|
||
pad_right: 0
|
||
pad_top: 0
|
||
pad_bottom: 0
|
||
on_click:
|
||
- script.execute: { id: set_light_color_temperature, kelvin: 2700 }
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "2700K"
|
||
text_font: sublabel_big
|
||
text_color: 0x3A2A18
|
||
clickable: false
|
||
- obj:
|
||
id: temp_preset_4000
|
||
x: 112
|
||
y: 8
|
||
width: 62
|
||
height: 30
|
||
radius: 15
|
||
bg_color: 0xFFE8C8
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
pad_left: 0
|
||
pad_right: 0
|
||
pad_top: 0
|
||
pad_bottom: 0
|
||
on_click:
|
||
- script.execute: { id: set_light_color_temperature, kelvin: 4000 }
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "4000K"
|
||
text_font: sublabel_big
|
||
text_color: 0x3A2A18
|
||
clickable: false
|
||
- obj:
|
||
id: temp_preset_6500
|
||
x: 200
|
||
y: 8
|
||
width: 62
|
||
height: 30
|
||
radius: 15
|
||
bg_color: 0xEAF4FF
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
pad_left: 0
|
||
pad_right: 0
|
||
pad_top: 0
|
||
pad_bottom: 0
|
||
on_click:
|
||
- script.execute: { id: set_light_color_temperature, kelvin: 6500 }
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "6500K"
|
||
text_font: sublabel_big
|
||
text_color: 0x1E2A36
|
||
clickable: false
|
||
|
||
- obj:
|
||
id: color_brightness_card
|
||
align: TOP_MID
|
||
y: 165
|
||
width: 188
|
||
height: 46
|
||
bg_opa: TRANSP
|
||
border_width: 0
|
||
scrollable: false
|
||
pad_left: 0
|
||
pad_right: 0
|
||
pad_top: 0
|
||
pad_bottom: 0
|
||
widgets:
|
||
- label:
|
||
x: 13
|
||
y: 6
|
||
text: "Brightness"
|
||
text_font: sublabel
|
||
text_color: 0xD1D1D6
|
||
hidden: true
|
||
clickable: false
|
||
- slider:
|
||
id: color_brightness_slider
|
||
x: 0
|
||
y: 0
|
||
width: 188
|
||
height: 46
|
||
min_value: 1
|
||
max_value: 100
|
||
value: 100
|
||
bg_color: 0x939391
|
||
bg_opa: COVER
|
||
radius: 23
|
||
border_width: 0
|
||
indicator:
|
||
bg_color: 0xFEC600
|
||
bg_opa: COVER
|
||
radius: 23
|
||
knob:
|
||
bg_opa: 0%
|
||
border_width: 0
|
||
shadow_width: 0
|
||
pad_all: 10
|
||
on_value:
|
||
- script.execute:
|
||
id: color_brightness_preview
|
||
value: !lambda "return x;"
|
||
on_release:
|
||
- script.execute:
|
||
id: color_brightness_commit
|
||
value: !lambda "return lv_slider_get_value(id(color_brightness_slider));"
|
||
- label:
|
||
id: color_brightness_value
|
||
align: RIGHT_MID
|
||
x: -13
|
||
text: "100 %"
|
||
text_font: sublabel_big
|
||
text_color: 0xFFFFFF
|
||
hidden: true
|
||
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: 10
|
||
y: 9
|
||
# 44 i.p.v. 36: iets groter doelvlak, groeit weg van de arc
|
||
# (top-left anchor blijft staan) i.p.v. ernaartoe.
|
||
width: 44
|
||
height: 44
|
||
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: -10
|
||
y: 9
|
||
width: 44
|
||
height: 44
|
||
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: 16
|
||
text: "Climate"
|
||
styles: style_room
|
||
|
||
- label:
|
||
id: climate_current_label
|
||
align: TOP_MID
|
||
y: 42
|
||
text: "Current: --"
|
||
text_font: sublabel_big
|
||
text_color: 0xD1E8FF
|
||
text_opa: 85%
|
||
clickable: false
|
||
|
||
- arc:
|
||
id: climate_arc
|
||
align: CENTER
|
||
y: 20
|
||
width: 184
|
||
height: 184
|
||
# 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: 20
|
||
arc_rounded: true
|
||
arc_color: 0x4B4B4F
|
||
arc_opa: 80%
|
||
bg_opa: TRANSP
|
||
border_width: 0
|
||
indicator:
|
||
arc_color: 0xFEC600
|
||
arc_opa: COVER
|
||
arc_width: 20
|
||
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: -16
|
||
text: "SET TO"
|
||
text_font: sublabel_big
|
||
text_color: 0xFFFFFF
|
||
text_opa: 68%
|
||
clickable: false
|
||
|
||
- label:
|
||
id: climate_target_label
|
||
align: CENTER
|
||
y: 20
|
||
text: "20.0°"
|
||
text_font: headline
|
||
text_color: 0xFFFFFF
|
||
clickable: false
|
||
|
||
- obj:
|
||
id: climate_minus_button
|
||
align: BOTTOM_LEFT
|
||
x: 38
|
||
y: -16
|
||
width: 48
|
||
height: 36
|
||
radius: 18
|
||
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: -38
|
||
y: -16
|
||
width: 48
|
||
height: 36
|
||
radius: 18
|
||
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: -18
|
||
width: 126
|
||
height: 34
|
||
radius: 17
|
||
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: 16
|
||
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: 50
|
||
width: 98%
|
||
height: 178
|
||
bg_opa: TRANSP
|
||
border_width: 0
|
||
scrollable: false
|
||
pad_all: 3
|
||
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: 132
|
||
height: 38
|
||
radius: 19
|
||
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: 132
|
||
height: 38
|
||
radius: 19
|
||
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: 132
|
||
height: 38
|
||
radius: 19
|
||
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: 132
|
||
height: 38
|
||
radius: 19
|
||
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: 132
|
||
height: 38
|
||
radius: 19
|
||
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: 132
|
||
height: 38
|
||
radius: 19
|
||
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: 132
|
||
height: 38
|
||
radius: 19
|
||
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: 10
|
||
y: 9
|
||
width: 36
|
||
height: 36
|
||
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: -10
|
||
y: 9
|
||
width: 36
|
||
height: 36
|
||
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: 16
|
||
text: "Vacuum"
|
||
text_font: sublabel_big
|
||
text_color: 0xFFFFFF
|
||
clickable: false
|
||
|
||
- obj:
|
||
id: vacuum_actions
|
||
align: TOP_MID
|
||
y: 52
|
||
width: 98%
|
||
height: 80
|
||
bg_opa: TRANSP
|
||
border_width: 0
|
||
scrollable: false
|
||
pad_all: 3
|
||
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: 72
|
||
height: 38
|
||
radius: 19
|
||
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: 72
|
||
height: 38
|
||
radius: 19
|
||
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: 72
|
||
height: 38
|
||
radius: 19
|
||
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: 72
|
||
height: 38
|
||
radius: 19
|
||
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: 138
|
||
text: "Suction"
|
||
text_font: sublabel
|
||
text_color: 0xFFFFFF
|
||
clickable: false
|
||
|
||
- obj:
|
||
id: vacuum_speeds
|
||
align: TOP_MID
|
||
y: 156
|
||
width: 98%
|
||
height: 74
|
||
bg_opa: TRANSP
|
||
border_width: 0
|
||
scrollable: false
|
||
pad_all: 3
|
||
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: 72
|
||
height: 32
|
||
radius: 19
|
||
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: "quiet" }
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Quiet"
|
||
text_font: sublabel_big
|
||
text_color: 0xFFFFFF
|
||
clickable: false
|
||
- obj:
|
||
id: vacspd_balanced
|
||
width: 72
|
||
height: 32
|
||
radius: 19
|
||
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: "balanced" }
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Balanced"
|
||
text_font: sublabel_big
|
||
text_color: 0xFFFFFF
|
||
clickable: false
|
||
- obj:
|
||
id: vacspd_turbo
|
||
width: 72
|
||
height: 32
|
||
radius: 19
|
||
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: "turbo" }
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Turbo"
|
||
text_font: sublabel_big
|
||
text_color: 0xFFFFFF
|
||
clickable: false
|
||
- obj:
|
||
id: vacspd_max
|
||
width: 72
|
||
height: 32
|
||
radius: 19
|
||
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: "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: 10
|
||
y: 9
|
||
width: 36
|
||
height: 36
|
||
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: -10
|
||
y: 9
|
||
width: 36
|
||
height: 36
|
||
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: 0x101820
|
||
bg_opa: COVER
|
||
pad_all: 0
|
||
border_width: 0
|
||
scrollable: false
|
||
widgets:
|
||
- label:
|
||
align: TOP_MID
|
||
y: 65
|
||
text: "Volg de kalibratiewizard"
|
||
text_font: sublabel
|
||
text_color: 0xFFFFFF
|
||
- obj:
|
||
x: 10
|
||
y: 10
|
||
width: 20
|
||
height: 20
|
||
pad_all: 0
|
||
border_width: 0
|
||
bg_opa: TRANSP
|
||
scrollable: false
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "+"
|
||
text_font: headline
|
||
text_color: 0xFFFFFF
|
||
- obj:
|
||
x: 289
|
||
y: 10
|
||
width: 20
|
||
height: 20
|
||
pad_all: 0
|
||
border_width: 0
|
||
bg_opa: TRANSP
|
||
scrollable: false
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "+"
|
||
text_font: headline
|
||
text_color: 0xFFFFFF
|
||
- obj:
|
||
x: 289
|
||
y: 209
|
||
width: 20
|
||
height: 20
|
||
pad_all: 0
|
||
border_width: 0
|
||
bg_opa: TRANSP
|
||
scrollable: false
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "+"
|
||
text_font: headline
|
||
text_color: 0xFFFFFF
|
||
- obj:
|
||
x: 10
|
||
y: 209
|
||
width: 20
|
||
height: 20
|
||
pad_all: 0
|
||
border_width: 0
|
||
bg_opa: TRANSP
|
||
scrollable: false
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "+"
|
||
text_font: headline
|
||
text_color: 0xFFFFFF
|
||
- obj:
|
||
x: 150
|
||
y: 110
|
||
width: 20
|
||
height: 20
|
||
pad_all: 0
|
||
border_width: 0
|
||
bg_opa: TRANSP
|
||
scrollable: false
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "+"
|
||
text_font: headline
|
||
text_color: 0xFFFFFF
|
||
|
||
script:
|
||
# 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: '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 = 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 = true;
|
||
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
|
||
- lvgl.widget.hide: brightness_overlay
|
||
- lvgl.widget.show: color_detail_overlay
|
||
- delay: 150ms
|
||
- lvgl.widget.hide: color_detail_overlay
|
||
- lvgl.widget.show: climate_detail_overlay
|
||
- delay: 150ms
|
||
- lvgl.widget.hide: climate_detail_overlay
|
||
- lvgl.widget.show: climate_mode_overlay
|
||
- delay: 150ms
|
||
- lvgl.widget.hide: climate_mode_overlay
|
||
- lvgl.widget.show: vacuum_overlay
|
||
- delay: 150ms
|
||
- 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 = 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 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 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 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 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);"
|
||
|
||
- id: open_color_detail
|
||
mode: restart
|
||
then:
|
||
- lambda: |-
|
||
lv_label_set_text(id(color_detail_title), id(active_title).c_str());
|
||
|
||
char buf[12];
|
||
snprintf(buf, sizeof(buf), "%d %%", id(active_brightness_pct));
|
||
lv_label_set_text(id(color_brightness_value), buf);
|
||
lv_slider_set_value(id(color_brightness_slider), id(active_brightness_pct), LV_ANIM_OFF);
|
||
|
||
bool show_tabs = id(active_supports_color) && id(active_supports_temperature);
|
||
if (show_tabs) {
|
||
lv_obj_clear_flag(id(color_mode_tabs), LV_OBJ_FLAG_HIDDEN);
|
||
} else {
|
||
lv_obj_add_flag(id(color_mode_tabs), LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
|
||
if (atoi("${DISPLAY_W}") < atoi("${DISPLAY_H}")) {
|
||
int palette_y = show_tabs ? 96 : 76;
|
||
lv_obj_set_size(id(color_palette_card), 150, 86);
|
||
lv_obj_set_y(id(color_palette_card), palette_y);
|
||
lv_obj_set_pos(id(color_preset_red), 13, 4);
|
||
lv_obj_set_pos(id(color_preset_orange), 60, 4);
|
||
lv_obj_set_pos(id(color_preset_yellow), 107, 4);
|
||
lv_obj_set_pos(id(color_preset_green), 13, 52);
|
||
lv_obj_set_pos(id(color_preset_blue), 60, 52);
|
||
lv_obj_set_pos(id(color_preset_purple), 107, 52);
|
||
|
||
lv_obj_set_size(id(temp_palette_card), 150, 126);
|
||
lv_obj_set_y(id(temp_palette_card), palette_y);
|
||
lv_obj_set_pos(id(temp_preset_2700), 44, 4);
|
||
lv_obj_set_pos(id(temp_preset_4000), 44, 48);
|
||
lv_obj_set_pos(id(temp_preset_6500), 44, 92);
|
||
|
||
lv_obj_set_y(id(color_brightness_card), 256);
|
||
} else {
|
||
int palette_y = show_tabs ? 98 : 78;
|
||
lv_obj_set_size(id(color_palette_card), 286, 48);
|
||
lv_obj_set_y(id(color_palette_card), palette_y);
|
||
lv_obj_set_pos(id(color_preset_red), 14, 8);
|
||
lv_obj_set_pos(id(color_preset_orange), 61, 8);
|
||
lv_obj_set_pos(id(color_preset_yellow), 108, 8);
|
||
lv_obj_set_pos(id(color_preset_green), 155, 8);
|
||
lv_obj_set_pos(id(color_preset_blue), 202, 8);
|
||
lv_obj_set_pos(id(color_preset_purple), 249, 8);
|
||
|
||
lv_obj_set_size(id(temp_palette_card), 286, 48);
|
||
lv_obj_set_y(id(temp_palette_card), palette_y);
|
||
lv_obj_set_pos(id(temp_preset_2700), 24, 8);
|
||
lv_obj_set_pos(id(temp_preset_4000), 112, 8);
|
||
lv_obj_set_pos(id(temp_preset_6500), 200, 8);
|
||
|
||
lv_obj_set_y(id(color_brightness_card), 165);
|
||
}
|
||
|
||
id(active_color_detail_tab) = id(active_supports_color) ? 0 : 1;
|
||
- lvgl.widget.hide: brightness_overlay
|
||
- lvgl.widget.show: color_detail_overlay
|
||
- script.execute:
|
||
id: show_color_detail_tab
|
||
tab: !lambda "return id(active_color_detail_tab);"
|
||
|
||
- id: show_color_detail_tab
|
||
mode: restart
|
||
parameters:
|
||
tab: int
|
||
then:
|
||
- lambda: |-
|
||
if (tab == 0 && !id(active_supports_color) && id(active_supports_temperature)) {
|
||
tab = 1;
|
||
} else if (tab == 1 && !id(active_supports_temperature) && id(active_supports_color)) {
|
||
tab = 0;
|
||
}
|
||
id(active_color_detail_tab) = tab;
|
||
|
||
bool show_tabs = id(active_supports_color) && id(active_supports_temperature);
|
||
if (show_tabs) {
|
||
lv_obj_clear_flag(id(color_mode_tabs), LV_OBJ_FLAG_HIDDEN);
|
||
} else {
|
||
lv_obj_add_flag(id(color_mode_tabs), LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
|
||
if (tab == 0 && id(active_supports_color)) {
|
||
lv_obj_clear_flag(id(color_palette_card), LV_OBJ_FLAG_HIDDEN);
|
||
lv_obj_add_flag(id(temp_palette_card), LV_OBJ_FLAG_HIDDEN);
|
||
if (show_tabs) lv_obj_set_x(id(color_tab_indicator), 2);
|
||
} else if (tab == 1 && id(active_supports_temperature)) {
|
||
lv_obj_add_flag(id(color_palette_card), LV_OBJ_FLAG_HIDDEN);
|
||
lv_obj_clear_flag(id(temp_palette_card), LV_OBJ_FLAG_HIDDEN);
|
||
if (show_tabs) lv_obj_set_x(id(color_tab_indicator), 95);
|
||
} else {
|
||
lv_obj_add_flag(id(color_palette_card), LV_OBJ_FLAG_HIDDEN);
|
||
lv_obj_add_flag(id(temp_palette_card), LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
|
||
lv_obj_set_style_text_color(
|
||
id(color_tab_label),
|
||
id(active_supports_color) ? lv_color_hex(0xFFFFFF) : lv_color_hex(0x8E8E93),
|
||
0
|
||
);
|
||
lv_obj_set_style_text_color(
|
||
id(temp_tab_label),
|
||
id(active_supports_temperature) ? lv_color_hex(0xFFFFFF) : lv_color_hex(0x8E8E93),
|
||
0
|
||
);
|
||
if (tab == 0 && id(active_supports_color)) {
|
||
lv_obj_set_style_text_color(id(color_tab_label), lv_color_hex(0xFFFFFF), 0);
|
||
lv_obj_set_style_text_color(id(temp_tab_label), lv_color_hex(0xD1D1D6), 0);
|
||
} else if (tab == 1 && id(active_supports_temperature)) {
|
||
lv_obj_set_style_text_color(id(color_tab_label), lv_color_hex(0xD1D1D6), 0);
|
||
lv_obj_set_style_text_color(id(temp_tab_label), lv_color_hex(0xFFFFFF), 0);
|
||
}
|
||
|
||
- id: color_brightness_preview
|
||
parameters:
|
||
value: int
|
||
then:
|
||
- lambda: |-
|
||
id(active_brightness_pct) = value;
|
||
char buf[12];
|
||
snprintf(buf, sizeof(buf), "%d %%", value);
|
||
lv_label_set_text(id(color_brightness_value), buf);
|
||
|
||
- id: color_brightness_commit
|
||
parameters:
|
||
value: int
|
||
then:
|
||
- lambda: 'id(active_brightness_pct) = value;'
|
||
- if:
|
||
condition:
|
||
lambda: 'return std::string("${DIRECT_ACTIONS}") == "true";'
|
||
then:
|
||
- homeassistant.action:
|
||
action: light.turn_on
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
brightness_pct: !lambda "return value;"
|
||
|
||
- id: set_light_color_name
|
||
mode: queued
|
||
parameters:
|
||
color: string
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: 'return std::string("${DIRECT_ACTIONS}") == "true";'
|
||
then:
|
||
- homeassistant.action:
|
||
action: light.turn_on
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
color_name: !lambda "return color;"
|
||
brightness_pct: !lambda "return id(active_brightness_pct);"
|
||
|
||
- id: set_light_color_temperature
|
||
mode: queued
|
||
parameters:
|
||
kelvin: int
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: 'return std::string("${DIRECT_ACTIONS}") == "true";'
|
||
then:
|
||
- homeassistant.action:
|
||
action: light.turn_on
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
color_temp_kelvin: !lambda "return kelvin;"
|
||
brightness_pct: !lambda "return id(active_brightness_pct);"
|
||
|
||
- 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), 196, 196);
|
||
lv_obj_set_y(id(climate_arc), 8);
|
||
lv_obj_set_y(id(climate_current_label), 48);
|
||
lv_obj_set_y(id(climate_minus_button), -62);
|
||
lv_obj_set_y(id(climate_plus_button), -62);
|
||
lv_obj_set_y(id(climate_mode_pill), -18);
|
||
} else {
|
||
lv_obj_set_size(id(climate_arc), 176, 176);
|
||
lv_obj_set_y(id(climate_arc), 18);
|
||
lv_obj_set_y(id(climate_current_label), 40);
|
||
lv_obj_set_y(id(climate_minus_button), -14);
|
||
lv_obj_set_y(id(climate_plus_button), -14);
|
||
lv_obj_set_y(id(climate_mode_pill), -15);
|
||
}
|
||
|
||
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:
|
||
- lvgl.widget.show: brightness_overlay
|
||
|
||
# UI refresh (values + time + styles) + render
|
||
- id: ui_refresh
|
||
mode: single
|
||
then:
|
||
- delay: 100ms
|
||
- lambda: |-
|
||
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});
|
||
|