61ceb40075
On 0.2.29 the edge swipe flipped pages, but about half of the quick flicks on Studio 1 ended with "randveeg niet gevuurd: 5 px naar binnen": every touch lasted about 160 ms and produced a single sample. A press in the empty band landed on tile_scroll or the page, and the theme gives every object a pressed style, so LVGL repainted nearly the whole screen on press and on release, blocking the loop and the 20 ms touch polling for as long as the flick took. The page and the tile container are no longer clickable: no state change, no repaint, no events, and the edge swipe on the touchscreen triggers does not need them. While an edge swipe is armed every sample is logged, and the press line carries the contact id, so the sampling can be read from the log. Firmware 0.2.30 for both boards. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
6723 lines
306 KiB
YAML
6723 lines
306 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) Icons come from the tile icon set in screen_manager/app/tile_icons.py; the
|
||
# icon fonts already contain all of them (tools/generate_icons.py).
|
||
# 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:
|
||
SCREEN_FIRMWARE_VERSION: "0.2.30"
|
||
# A tap survives a finger that drifts up to this far (320 px over 58 mm: 1 cm); further is a swipe.
|
||
TOUCH_MOVE_LIMIT_PX: "56"
|
||
# Shortest contact that counts as a tap; a resistive panel bounces, a capacitive one does not.
|
||
TOUCH_MIN_PRESS_MS: "60"
|
||
DYNAMIC_TILES: "false"
|
||
DASHBOARD_INBOX_INTERNAL: "true"
|
||
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
|
||
# The icon fonts carry the full tile icon set from screen_manager/app/tile_icons.py
|
||
# (tools/generate_icons.py). Pick TILEn_ICON from that set; no glyph list to maintain.
|
||
# ============================================================
|
||
|
||
# ============================================================
|
||
# 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 from the tile icon set (screen_manager/app/tile_icons.py)
|
||
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 from the tile icon set (screen_manager/app/tile_icons.py)
|
||
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 from the tile icon set (screen_manager/app/tile_icons.py)
|
||
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 from the tile icon set (screen_manager/app/tile_icons.py)
|
||
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 from the tile icon set (screen_manager/app/tile_icons.py)
|
||
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 from the tile icon set (screen_manager/app/tile_icons.py)
|
||
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 glyph from the tile icon set (screen_manager/app/tile_icons.py)
|
||
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 glyph from the tile icon set (screen_manager/app/tile_icons.py)
|
||
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 glyph from the tile icon set (screen_manager/app/tile_icons.py)
|
||
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 glyph from the tile icon set (screen_manager/app/tile_icons.py)
|
||
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: "0xE7E7E7" # lichte achtergrond, gelijk aan de Guition
|
||
BG_BOTTOM_COLOR: "0xE7E7E7"
|
||
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, smart_display]
|
||
|
||
esphome:
|
||
min_version: 2026.6.2
|
||
project:
|
||
name: maxgramser.esp_screen
|
||
version: "${SCREEN_FIRMWARE_VERSION}"
|
||
platformio_options:
|
||
build_flags:
|
||
- -DLV_USE_SWITCH=1
|
||
includes:
|
||
- cyd_ui.h
|
||
- light_controls.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);
|
||
cyd::touch_guard.configure(${TOUCH_MOVE_LIMIT_PX}, ${TOUCH_MIN_PRESS_MS});
|
||
id(last_touch_ms) = millis();
|
||
id(display_dimmed) = false;
|
||
lv_obj_set_style_pad_row(id(vacuum_actions), 4, 0);
|
||
lv_obj_set_style_pad_column(id(vacuum_actions), 4, 0);
|
||
lv_obj_set_style_pad_row(id(vacuum_speeds), 4, 0);
|
||
lv_obj_set_style_pad_column(id(vacuum_speeds), 4, 0);
|
||
- lambda: |-
|
||
auto &settings = screen_settings::current;
|
||
settings.standby_enabled = std::string("${AUTO_DIM}") == "true";
|
||
settings.standby_seconds = atoi("${AUTO_DIM_TIMEOUT}");
|
||
settings.brightness = atoi("${MAX_BRIGHTNESS}");
|
||
settings.standby_brightness = atoi("${DIM_BRIGHTNESS}");
|
||
settings.night_enabled = std::string("${NIGHT_MODE}") == "true";
|
||
settings.night_start = atoi("${NIGHT_START_HOUR}") * 60;
|
||
settings.night_end = atoi("${NIGHT_END_HOUR}") * 60;
|
||
settings.night_brightness = atoi("${NIGHT_DIM_BRIGHTNESS}");
|
||
runtime_tiles::watch_font = id(headline)->get_lv_font();
|
||
runtime_tiles::watch_value_font = id(watch_value)->get_lv_font();
|
||
runtime_tiles::watch_icon_font = id(watch_icon)->get_lv_font();
|
||
runtime_tiles::clock_font = id(clock_digits)->get_lv_font();
|
||
runtime_tiles::now_time = []() { return id(homeassistant_time).now(); };
|
||
runtime_tiles::mini_icon_font = id(materialdesign_icons_mini)->get_lv_font();
|
||
runtime_tiles::control_font = id(sublabel_big)->get_lv_font();
|
||
runtime_tiles::enabled = std::string("${DYNAMIC_TILES}") == "true";
|
||
if (!runtime_tiles::enabled) return;
|
||
runtime_tiles::load_settings();
|
||
runtime_tiles::settings_changed = []() { id(apply_screen_settings).execute(); };
|
||
runtime_tiles::dismiss = []() {
|
||
runtime_tiles::hide_detail();
|
||
id(active_entity).clear();
|
||
lv_obj_add_flag(id(brightness_overlay), LV_OBJ_FLAG_HIDDEN);
|
||
lv_obj_add_flag(id(color_detail_overlay), LV_OBJ_FLAG_HIDDEN);
|
||
lv_obj_add_flag(id(climate_detail_overlay), LV_OBJ_FLAG_HIDDEN);
|
||
lv_obj_add_flag(id(climate_mode_overlay), LV_OBJ_FLAG_HIDDEN);
|
||
lv_obj_add_flag(id(vacuum_overlay), LV_OBJ_FLAG_HIDDEN);
|
||
};
|
||
runtime_tiles::layout_changed = []() { id(show_tile_page).execute(); };
|
||
lv_obj_add_event_cb(id(home_page)->obj, [](lv_event_t *event) {
|
||
if(!runtime_tiles::enabled || !runtime_tiles::swipe_pages || id(display_dimmed) || id(calibration_active) ||
|
||
!id(active_entity).empty() || (runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root,LV_OBJ_FLAG_HIDDEN)))return;
|
||
auto *input=lv_indev_active();if(!input)return;
|
||
auto direction=lv_indev_get_gesture_dir(input);
|
||
if(direction!=LV_DIR_LEFT && direction!=LV_DIR_RIGHT)return;
|
||
cyd::touch_guard.consume();
|
||
lv_indev_wait_release(input);
|
||
id(tile_page)+=direction==LV_DIR_LEFT?1:-1;
|
||
id(show_tile_page).execute();
|
||
}, LV_EVENT_GESTURE, nullptr);
|
||
runtime_tiles::refresh = []() { if (!id(ui_refresh).is_running()) id(ui_refresh).execute(); };
|
||
runtime_tiles::bind(0, id(tile1), id(t1_title), id(t1_value), id(tile1_icon_circle), id(tile1_icon_lbl));
|
||
runtime_tiles::bind(1, id(tile2), id(t2_title), id(t2_value), id(tile2_icon_circle), id(tile2_icon_lbl));
|
||
runtime_tiles::bind(2, id(tile3), id(t3_title), id(t3_value), id(tile3_icon_circle), id(tile3_icon_lbl));
|
||
runtime_tiles::bind(3, id(tile4), id(t4_title), id(t4_value), id(tile4_icon_circle), id(tile4_icon_lbl));
|
||
runtime_tiles::bind(4, id(tile5), id(t5_title), id(t5_value), id(tile5_icon_circle), id(tile5_icon_lbl));
|
||
runtime_tiles::bind(5, id(tile6), id(t6_title), id(t6_value), id(tile6_icon_circle), id(tile6_icon_lbl));
|
||
runtime_tiles::bind(6, id(tile7), id(t7_title), id(t7_value), id(tile7_icon_circle), id(tile7_icon_lbl));
|
||
runtime_tiles::bind(7, id(tile8), id(t8_title), id(t8_value), id(tile8_icon_circle), id(tile8_icon_lbl));
|
||
runtime_tiles::bind(8, id(tile9), id(t9_title), id(t9_value), id(tile9_icon_circle), id(tile9_icon_lbl));
|
||
runtime_tiles::bind(9, id(tile10), id(t10_title), id(t10_value), id(tile10_icon_circle), id(tile10_icon_lbl));
|
||
runtime_tiles::detail = [](runtime_tiles::Tile &t) {
|
||
if (t.domain() == "vacuum") {
|
||
lv_obj_t *buttons[] = {id(vacspd_quiet), id(vacspd_balanced), id(vacspd_turbo), id(vacspd_max)};
|
||
for (unsigned i = 0; i < 4; ++i) {
|
||
if (i < t.fan_speed_count) {
|
||
lv_obj_clear_flag(buttons[i], LV_OBJ_FLAG_HIDDEN);
|
||
lv_label_set_text(lv_obj_get_child(buttons[i], 0), t.fan_speeds[i].c_str());
|
||
} else lv_obj_add_flag(buttons[i], LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
id(open_vacuum_overlay).execute(t.name, t.entity);
|
||
return;
|
||
}
|
||
light_controls::fallback = light_controls::State{};
|
||
light_controls::fallback.hue = t.hue;
|
||
light_controls::fallback.kelvin = t.kelvin;
|
||
light_controls::fallback.minimum = t.min_kelvin;
|
||
light_controls::fallback.maximum = t.max_kelvin;
|
||
id(active_climate_fan_modes) = t.fan_modes; id(active_climate_fan_mode) = t.fan_mode;
|
||
id(active_climate_swing_modes) = t.swing_modes; id(active_climate_swing_mode) = t.swing_mode;
|
||
id(open_value_overlay).execute(t.name, t.entity, t.domain(), "auto", t.active(), 0,
|
||
t.brightness, t.percentage, t.position, runtime_tiles::icon_for(t),
|
||
0xD5EEFC, 0x00A6ED, 0x1B1B1B, "Uit", "Aan", t.modes,
|
||
t.current, t.target, t.humidity, t.state, t.hvac_modes,
|
||
std::to_string(t.minimum), std::to_string(t.maximum), std::to_string(t.step));
|
||
};
|
||
runtime_tiles::detail_update = [](runtime_tiles::Tile &t) {
|
||
if (id(active_entity) != t.entity || id(touch_down)) return;
|
||
if (t.domain() == "climate" && !lv_obj_has_flag(id(climate_detail_overlay), LV_OBJ_FLAG_HIDDEN)) {
|
||
if (id(climate_edit_pending) && millis() - id(climate_edit_ms) < 3000) return;
|
||
id(climate_edit_pending) = false;
|
||
id(active_climate_mode) = t.state;
|
||
id(active_climate_supported_modes) = t.hvac_modes;
|
||
lv_label_set_text(id(climate_mode_label), tile_controls::climate_mode_text(t.state));
|
||
id(climate_power_refresh).execute();
|
||
id(active_climate_fan_modes) = t.fan_modes; id(active_climate_fan_mode) = t.fan_mode;
|
||
id(active_climate_swing_modes) = t.swing_modes; id(active_climate_swing_mode) = t.swing_mode;
|
||
char buf[72];
|
||
if (std::isfinite(t.current)) snprintf(buf, sizeof(buf), "Nu: %.1f°", t.current);
|
||
else snprintf(buf, sizeof(buf), "Nu: --");
|
||
lv_label_set_text(id(climate_current_label), buf);
|
||
if (std::isfinite(t.target)) {
|
||
int target = cyd::quantize_temperature(lround(t.target * 10), id(active_climate_min_x10), id(active_climate_max_x10), id(active_climate_step_x10));
|
||
id(active_climate_target_x10) = target;
|
||
snprintf(buf, sizeof(buf), "%.1f°", target / 10.0f);
|
||
lv_label_set_text(id(climate_target_label), buf);
|
||
lv_arc_set_value(id(climate_arc), target);
|
||
}
|
||
}
|
||
};
|
||
- lambda: |-
|
||
static const char *entities[] = {"${TILE1_ENTITY}", "${TILE2_ENTITY}", "${TILE3_ENTITY}", "${TILE4_ENTITY}", "${TILE5_ENTITY}", "${TILE6_ENTITY}", "${TILE7_ENTITY}", "${TILE9_ENTITY}"};
|
||
if (!runtime_tiles::enabled) light_controls::subscribe(entities, 8);
|
||
light_controls::setup(id(color_detail_overlay), id(sublabel)->get_lv_font(), atoi("${DISPLAY_W}"), atoi("${DISPLAY_H}"));
|
||
light_controls::commits[0] = [](int value) { id(set_light_hue).execute(value); };
|
||
light_controls::commits[1] = [](int value) { id(set_light_color_temperature).execute(value); };
|
||
light_controls::commits[2] = [](int value) { id(color_brightness_commit).execute(value); };
|
||
- lambda: |-
|
||
if (!runtime_tiles::enabled) return;
|
||
screen_calibration::apply = [](const screen_calibration::Calibration &cal) {
|
||
id(ts_touch).set_raw_correction(cal.c[0],cal.c[1],cal.c[2],cal.c[3],cal.c[4],cal.c[5]);
|
||
};
|
||
screen_calibration::isolation = [](bool active) { id(calibration_active) = active; };
|
||
screen_calibration::setup(id(home_page)->obj, id(sublabel)->get_lv_font(), id(headline)->get_lv_font(),
|
||
${TOUCH_CAL_X_MIN}, ${TOUCH_CAL_X_MAX}, ${TOUCH_CAL_Y_MIN}, ${TOUCH_CAL_Y_MAX});
|
||
- light.turn_on:
|
||
id: back_light
|
||
brightness: !lambda "return screen_settings::current.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: |-
|
||
const auto &cfg = screen_settings::current;
|
||
ESP_LOGI("ui_diag", "settings standby=%d timeout=%d brightness=%d dim=%d night=%d start=%d end=%d night_dim=%d clock=%d clock24=%d home=%d idle_ms=%u dimmed=%d",
|
||
cfg.standby_enabled, cfg.standby_seconds, cfg.brightness, cfg.standby_brightness,
|
||
cfg.night_enabled, cfg.night_start, cfg.night_end, cfg.night_brightness,
|
||
cfg.show_clock, cfg.clock_24h, cfg.home_on_standby,
|
||
(unsigned)(millis() - id(last_touch_ms)), id(display_dimmed));
|
||
ESP_LOGI("ui_diag", "navigation page=%d runtime_detail=%d", id(tile_page),
|
||
runtime_tiles::detail_root && !lv_obj_has_flag(runtime_tiles::detail_root, LV_OBJ_FLAG_HIDDEN));
|
||
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: preview_runtime_card
|
||
variables:
|
||
index: int
|
||
then:
|
||
- lambda: |-
|
||
if (index < 0) { runtime_tiles::hide_detail(); if (runtime_tiles::dismiss) runtime_tiles::dismiss(); return; }
|
||
if (runtime_tiles::enabled && index < (int)runtime_tiles::model.count) {
|
||
runtime_tiles::active_index = index;
|
||
auto &tile = runtime_tiles::model.tiles[index];
|
||
auto domain = tile.domain();
|
||
// Lights, fans, covers and climate open the board's own card, like a long press does.
|
||
if ((domain == "light" || domain == "fan" || domain == "cover" || domain == "climate") && runtime_tiles::detail) {
|
||
tile.begin(millis(), true);
|
||
runtime_tiles::detail(tile);
|
||
return;
|
||
}
|
||
runtime_tiles::show_detail(index);
|
||
}
|
||
# Diagnostics: the climate mode picker and the busy sheet of a tile, without touching the glass or HA.
|
||
- action: preview_climate_picker
|
||
then:
|
||
- script.execute: open_climate_mode_picker
|
||
- action: preview_busy
|
||
variables:
|
||
index: int
|
||
then:
|
||
- lambda: |-
|
||
if (runtime_tiles::enabled && index >= 0 && index < (int)runtime_tiles::model.count) {
|
||
runtime_tiles::model.tiles[index].begin(millis());
|
||
if (runtime_tiles::refresh) runtime_tiles::refresh();
|
||
}
|
||
- action: light_controls_preview
|
||
then:
|
||
- script.execute: light_controls_preview
|
||
- 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
|
||
# Mains-powered panel: no modem sleep, so HA updates and OTA arrive without beacon latency.
|
||
power_save_mode: none
|
||
ap:
|
||
ssid: "Smartdisplay Fallback Hotspot"
|
||
password: !secret ap_password
|
||
|
||
captive_portal:
|
||
|
||
interval:
|
||
- interval: 250ms
|
||
then:
|
||
- lambda: runtime_tiles::tick();
|
||
- 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 (!screen_settings::current.standby_enabled) return false;
|
||
if (id(display_dimmed) || id(touch_down) || id(calibration_active)) return false;
|
||
return (millis() - id(last_touch_ms)) > (screen_settings::current.standby_seconds * 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:
|
||
- script.execute: apply_screen_settings
|
||
- lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();'
|
||
on_time:
|
||
- seconds: 0
|
||
minutes: /1
|
||
then:
|
||
- script.execute: apply_screen_settings
|
||
- lambda: 'if (!id(ui_refresh).is_running()) id(ui_refresh).execute();'
|
||
|
||
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_power_on
|
||
type: bool
|
||
restore_value: no
|
||
initial_value: 'false'
|
||
|
||
- id: active_climate_fan_modes
|
||
type: std::string
|
||
restore_value: no
|
||
|
||
- id: active_climate_fan_mode
|
||
type: std::string
|
||
restore_value: no
|
||
|
||
- id: active_climate_swing_modes
|
||
type: std::string
|
||
restore_value: no
|
||
|
||
- id: active_climate_swing_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-500.ttf"
|
||
id: watch_value
|
||
size: 22
|
||
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',
|
||
'ä','ö','ü','Ä','Ö','Ü','ß','/','·']
|
||
|
||
# Clock card digits only; the date line reuses the tile title font.
|
||
- file: "fonts/Roboto-500.ttf"
|
||
id: clock_digits
|
||
size: 24
|
||
bpp: 4
|
||
glyphs: [':','-','0','1','2','3','4','5','6','7','8','9']
|
||
|
||
- 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: &tile_icons # GENERATED by tools/generate_icons.py from screen_manager/app/tile_icons.py
|
||
- "\U000F0335" # lightbulb
|
||
- "\U000F1253" # lightbulb-group
|
||
- "\U000F0769" # ceiling-light
|
||
- "\U000F1793" # chandelier
|
||
- "\U000F06B5" # lamp
|
||
- "\U000F08DD" # floor-lamp
|
||
- "\U000F095F" # desk-lamp
|
||
- "\U000F091D" # wall-sconce-flat
|
||
- "\U000F179B" # light-recessed
|
||
- "\U000F04C9" # spotlight-beam
|
||
- "\U000F1051" # led-strip-variant
|
||
- "\U000F1054" # outdoor-lamp
|
||
- "\U000F02DC" # home
|
||
- "\U000F04B9" # sofa
|
||
- "\U000F02E3" # bed
|
||
- "\U000F0A70" # silverware-fork-knife
|
||
- "\U000F0B7C" # chef-hat
|
||
- "\U000F09A0" # shower
|
||
- "\U000F1818" # bathtub
|
||
- "\U000F09AB" # toilet
|
||
- "\U000F1239" # desk
|
||
- "\U000F06D9" # garage
|
||
- "\U000F0E2E" # fireplace
|
||
- "\U000F050F" # thermometer
|
||
- "\U000F0393" # thermostat
|
||
- "\U000F001B" # air-conditioner
|
||
- "\U000F0438" # radiator
|
||
- "\U000F1A43" # heat-pump
|
||
- "\U000F0238" # fire
|
||
- "\U000F0717" # snowflake
|
||
- "\U000F058E" # water-percent
|
||
- "\U000F0210" # fan
|
||
- "\U000F1797" # ceiling-fan
|
||
- "\U000F0D44" # air-purifier
|
||
- "\U000F07E4" # molecule-co2
|
||
- "\U000F0599" # weather-sunny
|
||
- "\U000F0595" # weather-partly-cloudy
|
||
- "\U000F0590" # weather-cloudy
|
||
- "\U000F0597" # weather-rainy
|
||
- "\U000F0598" # weather-snowy
|
||
- "\U000F059D" # weather-windy
|
||
- "\U000F0594" # weather-night
|
||
- "\U000F059C" # weather-sunset-up
|
||
- "\U000F0387" # music-note
|
||
- "\U000F075A" # music
|
||
- "\U000F0CB8" # playlist-music
|
||
- "\U000F0025" # album
|
||
- "\U000F099A" # record-player
|
||
- "\U000F04C7" # spotify
|
||
- "\U000F0439" # radio
|
||
- "\U000F0994" # podcast
|
||
- "\U000F04C3" # speaker
|
||
- "\U000F0D38" # speaker-multiple
|
||
- "\U000F17DB" # soundbar
|
||
- "\U000F02CB" # headphones
|
||
- "\U000F067D" # piano
|
||
- "\U000F0771" # guitar-acoustic
|
||
- "\U000F0502" # television
|
||
- "\U000F042E" # projector
|
||
- "\U000F0FCE" # movie-open
|
||
- "\U000F0118" # cast
|
||
- "\U000F0297" # gamepad-variant
|
||
- "\U000F0EC5" # remote-tv
|
||
- "\U000F033E" # lock
|
||
- "\U000F0FC6" # lock-open-variant
|
||
- "\U000F068A" # shield-home
|
||
- "\U000F078F" # alarm-light
|
||
- "\U000F009A" # bell
|
||
- "\U000F12E6" # doorbell
|
||
- "\U000F07AE" # cctv
|
||
- "\U000F0D91" # motion-sensor
|
||
- "\U000F081B" # door-closed
|
||
- "\U000F081C" # door-open
|
||
- "\U000F11DB" # window-closed-variant
|
||
- "\U000F0392" # smoke-detector
|
||
- "\U000F1502" # water-alert
|
||
- "\U000F072A" # washing-machine
|
||
- "\U000F0917" # tumble-dryer
|
||
- "\U000F0AAC" # dishwasher
|
||
- "\U000F0290" # fridge
|
||
- "\U000F04DE" # stove
|
||
- "\U000F0C99" # microwave
|
||
- "\U000F0CD3" # toaster-oven
|
||
- "\U000F109F" # coffee-maker
|
||
- "\U000F05FA" # kettle
|
||
- "\U000F070D" # robot-vacuum
|
||
- "\U000F11F7" # robot-mower
|
||
- "\U000F042B" # printer-3d
|
||
- "\U000F06A5" # power-plug
|
||
- "\U000F07E7" # power-socket-eu
|
||
- "\U000F0322" # laptop
|
||
- "\U000F0469" # router-wireless
|
||
- "\U000F0241" # flash
|
||
- "\U000F0A72" # solar-power
|
||
- "\U000F0D9B" # solar-panel
|
||
- "\U000F1901" # home-battery
|
||
- "\U000F12A3" # battery-high
|
||
- "\U000F05F1" # ev-station
|
||
- "\U000F0B6C" # car-electric
|
||
- "\U000F1A57" # meter-electric
|
||
- "\U000F1A59" # meter-gas
|
||
- "\U000F058C" # water
|
||
- "\U000F0F92" # water-boiler
|
||
- "\U000F029A" # gauge
|
||
- "\U000F111C" # window-shutter
|
||
- "\U000F111E" # window-shutter-open
|
||
- "\U000F00AC" # blinds
|
||
- "\U000F1011" # blinds-open
|
||
- "\U000F1A6C" # roller-shade-closed
|
||
- "\U000F1846" # curtains
|
||
- "\U000F1847" # curtains-closed
|
||
- "\U000F1B88" # awning-outline
|
||
- "\U000F024A" # flower
|
||
- "\U000F0531" # tree
|
||
- "\U000F0E66" # sprout
|
||
- "\U000F1510" # grass
|
||
- "\U000F105F" # sprinkler
|
||
- "\U000F0606" # pool
|
||
- "\U000F0E45" # grill
|
||
- "\U000F06EE" # mailbox
|
||
- "\U000F0A79" # trash-can
|
||
- "\U000F044C" # recycle
|
||
- "\U000F0A43" # dog
|
||
- "\U000F011B" # cat
|
||
- "\U000F0004" # account
|
||
- "\U000F0849" # account-group
|
||
- "\U000F0A89" # account-child
|
||
- "\U000F0826" # home-account
|
||
- "\U000F04B2" # sleep
|
||
- "\U000F070E" # run
|
||
- "\U000F00D6" # briefcase
|
||
- "\U000F010B" # car
|
||
- "\U000F00A3" # bike
|
||
- "\U000F001D" # airplane
|
||
- "\U000F034E" # map-marker
|
||
- "\U000F0425" # power
|
||
- "\U000F0521" # toggle-switch
|
||
- "\U000F12A8" # gesture-tap-button
|
||
- "\U000F040A" # play
|
||
- "\U000F0BC2" # script-text
|
||
- "\U000F03D8" # palette
|
||
- "\U000F0020" # alarm
|
||
- "\U000F0150" # clock-outline
|
||
- "\U000F051B" # timer-outline
|
||
- "\U000F00ED" # calendar
|
||
- "\U000F009E" # bell-ring
|
||
- "\U000F02D1" # heart
|
||
- "\U000F002A" # alert-outline
|
||
- "\U000F0493" # cog
|
||
- "\U000F00E2" # broom
|
||
- "\U000F05D6" # alert-circle-outline
|
||
- "\U000F012C" # check
|
||
- "\U000F03E4" # pause
|
||
- "\U000F04DB" # stop
|
||
- "\U000F04AD" # skip-next
|
||
- "\U000F04AE" # skip-previous
|
||
- "\U000F057E" # volume-high
|
||
- "\U000F0581" # volume-off
|
||
- "\U000F005D" # arrow-up
|
||
- "\U000F0045" # arrow-down
|
||
- "\U000F084E" # arrow-expand-horizontal
|
||
- "\U000F084C" # arrow-collapse-horizontal
|
||
- "\U000F05F8" # home-map-marker
|
||
- "\U000F0415" # plus
|
||
- "\U000F0374" # minus
|
||
- "\U000F0141" # chevron-left
|
||
- "\U000F0142" # chevron-right
|
||
- "\U000F0156" # close
|
||
- "\U000F1A79" # sun-snowflake-variant
|
||
- "\U000F1B17" # thermostat-auto
|
||
- "\U000F0591" # weather-fog
|
||
- "\U000F0592" # weather-hail
|
||
- "\U000F0593" # weather-lightning
|
||
- "\U000F067E" # weather-lightning-rainy
|
||
- "\U000F0596" # weather-pouring
|
||
- "\U000F067F" # weather-snowy-rainy
|
||
- "\U000F059B" # weather-sunset-down
|
||
|
||
- file: 'fonts/materialdesignicons-webfont.ttf'
|
||
id: materialdesign_icons_mini
|
||
size: 18
|
||
glyphs: *tile_icons
|
||
|
||
- file: 'fonts/materialdesignicons-webfont.ttf'
|
||
id: watch_icon
|
||
size: 12
|
||
glyphs: *tile_icons
|
||
|
||
# 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
|
||
name: "Schermfirmware"
|
||
entity_category: diagnostic
|
||
lambda: 'return {"${SCREEN_FIRMWARE_VERSION}"};'
|
||
update_interval: 60s
|
||
# ESP Screen Manager matches the screen to its YAML profile and OTA address.
|
||
- platform: template
|
||
name: "Apparaatnaam"
|
||
entity_category: diagnostic
|
||
lambda: 'return {"${DEVICE_NAME}"};'
|
||
update_interval: 60s
|
||
- platform: wifi_info
|
||
ip_address:
|
||
name: "IP-adres"
|
||
entity_category: diagnostic
|
||
- 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, touch.id);
|
||
screen_calibration::press();
|
||
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();
|
||
// Only the contact that started the touch counts; the guard ignores other ids.
|
||
for (const auto &point : touches) {
|
||
cyd::touch_guard.update(point.x, point.y, point.id);
|
||
}
|
||
on_release:
|
||
- lambda: |-
|
||
id(last_touch_ms) = millis();
|
||
id(touch_down) = false;
|
||
screen_calibration::release(id(ts_touch).filtered_raw_x(), id(ts_touch).filtered_raw_y());
|
||
|
||
# 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: 1
|
||
border_color: 0xDDDDDD
|
||
border_opa: COVER
|
||
pad_left: 8
|
||
pad_right: 8
|
||
pad_top: 8
|
||
pad_bottom: 8
|
||
shadow_width: 0
|
||
shadow_opa: 40%
|
||
|
||
- id: style_tile_disabled
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
radius: 18
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
border_opa: COVER
|
||
pad_left: 8
|
||
pad_right: 8
|
||
pad_top: 8
|
||
pad_bottom: 8
|
||
shadow_width: 0
|
||
shadow_opa: 25%
|
||
|
||
- id: style_icon_circle
|
||
bg_color: 0xD5EEFC
|
||
bg_opa: COVER
|
||
radius: 999
|
||
border_width: 0
|
||
|
||
- id: style_icon_circle_disabled
|
||
bg_color: 0xEEEEEE
|
||
bg_opa: COVER
|
||
radius: 999
|
||
border_width: 0
|
||
|
||
- id: style_room
|
||
text_font: headline
|
||
text_color: 0x1B1B1B
|
||
text_opa: 100%
|
||
|
||
- id: style_time
|
||
text_font: time_label
|
||
text_color: 0x1B1B1B
|
||
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: 0x1B1B1B
|
||
text_opa: 80%
|
||
|
||
- id: style_title_disabled
|
||
text_font: label
|
||
text_color: 0x1B1B1B
|
||
text_opa: 100%
|
||
|
||
- id: style_value_disabled
|
||
text_font: sublabel
|
||
text_color: 0x1B1B1B
|
||
text_opa: 100%
|
||
|
||
pages:
|
||
- id: home_page
|
||
styles: style_bg_grad
|
||
pad_all: 0
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
widgets:
|
||
# Never shown: it only makes ESPHome compile LVGL's spinner, which the busy sheet on a tile draws.
|
||
- spinner:
|
||
id: busy_spinner_seed
|
||
hidden: true
|
||
width: 2
|
||
height: 2
|
||
spin_time: 1s
|
||
arc_length: 60deg
|
||
- label:
|
||
id: lbl_room
|
||
x: 11
|
||
y: ${HEADER_Y}
|
||
text: "${ROOM_NAME}"
|
||
styles: style_room
|
||
|
||
# Enables LVGL's line widget for the runtime clock hands and sparklines.
|
||
- line:
|
||
id: runtime_line_enable
|
||
hidden: true
|
||
points:
|
||
- 0, 0
|
||
- 1, 1
|
||
|
||
- 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 !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 1);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile1_press" }
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: "${TILE1_TYPE}"
|
||
tap_action: "${TILE1_TAP_ACTION}"
|
||
service: "${TILE1_TAP_SERVICE}"
|
||
entity_id: "${TILE1_ENTITY}"
|
||
param_key: "${TILE1_TAP_PARAM_KEY}"
|
||
param_val: "${TILE1_TAP_PARAM_VAL}"
|
||
was_on: !lambda |-
|
||
if (std::string("${TILE1_TYPE}") == "cover")
|
||
return id(tile1_cover_state).state != "closed";
|
||
return std::string(id(ha_state_tile1).state.c_str()) == "on";
|
||
on_long_press:
|
||
- if:
|
||
condition:
|
||
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 1);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile1_long_press" }
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE1_LONGPRESS}\") == \"action\";"
|
||
then:
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: !lambda |-
|
||
std::string t = "${TILE1_LONGPRESS_ACTION_TYPE}";
|
||
return t.empty() ? std::string("${TILE1_TYPE}") : t;
|
||
tap_action: "activate"
|
||
service: "${TILE1_LONGPRESS_ACTION_SERVICE}"
|
||
entity_id: "${TILE1_LONGPRESS_ACTION}"
|
||
param_key: ""
|
||
param_val: ""
|
||
was_on: false
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE1_LONGPRESS}\") == \"slider\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_value_overlay
|
||
title: "${TILE1_TITLE}"
|
||
entity: "${TILE1_ENTITY}"
|
||
tile_type: "${TILE1_TYPE}"
|
||
longpress_mode: "${TILE1_LONGPRESS_SLIDER}"
|
||
was_on: !lambda |-
|
||
if (std::string("${TILE1_TYPE}") == "cover")
|
||
return id(tile1_cover_state).state != "closed";
|
||
return std::string(id(ha_state_tile1).state.c_str()) == "on";
|
||
off_value: !lambda "return atoi(\"${TILE1_LONGPRESS_OFF_VALUE}\");"
|
||
brightness_value: !lambda "return id(tile1_brightness).state;"
|
||
percentage_value: !lambda "return id(tile1_percentage).state;"
|
||
position_value: !lambda "return id(tile1_cover_position).state;"
|
||
icon_text: "${TILE1_ICON}"
|
||
slider_bg_color: ${TILE1_BG_DISABLED_COLOR}
|
||
slider_fill_color: ${TILE1_CIRCLE_ACTIVE_COLOR}
|
||
slider_icon_color: ${TILE1_ICON_ACTIVE_COLOR}
|
||
label_off: "${TILE1_LABEL_OFF}"
|
||
label_on: "${TILE1_LABEL_ON}"
|
||
supported_color_modes: !lambda "return std::string(id(tile1_supported_color_modes).state.c_str());"
|
||
climate_current_temperature: !lambda "return id(tile1_climate_current_temperature).state;"
|
||
climate_target_temperature: !lambda "return id(tile1_climate_target_temperature).state;"
|
||
climate_humidity: !lambda "return id(tile1_climate_humidity).state;"
|
||
climate_mode: !lambda "return std::string(id(tile1_climate_mode).state.c_str());"
|
||
climate_supported_modes: !lambda "return std::string(id(tile1_climate_supported_modes).state.c_str());"
|
||
climate_min_temp: "${TILE1_CLIMATE_MIN_TEMP}"
|
||
climate_max_temp: "${TILE1_CLIMATE_MAX_TEMP}"
|
||
climate_step: "${TILE1_CLIMATE_STEP}"
|
||
# Long-press modus "vacuum": opent de stofzuigerbediening.
|
||
# Valt buiten de action/slider-keten, dus die blijft ongewijzigd.
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE1_LONGPRESS}\") == \"vacuum\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_vacuum_overlay
|
||
title: "${TILE1_TITLE}"
|
||
entity: "${TILE1_ENTITY}"
|
||
|
||
# ---------- TILE 2 ----------
|
||
- obj:
|
||
id: tile2
|
||
x: ${GRID_COL2_X}
|
||
y: ${GRID_ROW1_Y}
|
||
width: ${TILE_W}
|
||
height: ${TILE_H}
|
||
styles: style_tile
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
# Directe lokale feedback: LVGL past dit toe zodra de vinger het
|
||
# glas raakt, in dezelfde frame. Onafhankelijk van Home Assistant.
|
||
pressed:
|
||
bg_opa: 45%
|
||
widgets:
|
||
- obj:
|
||
id: tile2_icon_circle
|
||
x: ${TILE_ICON_X}
|
||
y: ${TILE_ICON_Y}
|
||
width: 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 !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 2);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile2_press" }
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: "${TILE2_TYPE}"
|
||
tap_action: "${TILE2_TAP_ACTION}"
|
||
service: "${TILE2_TAP_SERVICE}"
|
||
entity_id: "${TILE2_ENTITY}"
|
||
param_key: "${TILE2_TAP_PARAM_KEY}"
|
||
param_val: "${TILE2_TAP_PARAM_VAL}"
|
||
was_on: !lambda |-
|
||
if (std::string("${TILE2_TYPE}") == "cover")
|
||
return id(tile2_cover_state).state != "closed";
|
||
return std::string(id(ha_state_tile2).state.c_str()) == "on";
|
||
on_long_press:
|
||
- if:
|
||
condition:
|
||
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 2);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile2_long_press" }
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE2_LONGPRESS}\") == \"action\";"
|
||
then:
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: !lambda |-
|
||
std::string t = "${TILE2_LONGPRESS_ACTION_TYPE}";
|
||
return t.empty() ? std::string("${TILE2_TYPE}") : t;
|
||
tap_action: "activate"
|
||
service: "${TILE2_LONGPRESS_ACTION_SERVICE}"
|
||
entity_id: "${TILE2_LONGPRESS_ACTION}"
|
||
param_key: ""
|
||
param_val: ""
|
||
was_on: false
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE2_LONGPRESS}\") == \"slider\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_value_overlay
|
||
title: "${TILE2_TITLE}"
|
||
entity: "${TILE2_ENTITY}"
|
||
tile_type: "${TILE2_TYPE}"
|
||
longpress_mode: "${TILE2_LONGPRESS_SLIDER}"
|
||
was_on: !lambda |-
|
||
if (std::string("${TILE2_TYPE}") == "cover")
|
||
return id(tile2_cover_state).state != "closed";
|
||
return std::string(id(ha_state_tile2).state.c_str()) == "on";
|
||
off_value: !lambda "return atoi(\"${TILE2_LONGPRESS_OFF_VALUE}\");"
|
||
brightness_value: !lambda "return id(tile2_brightness).state;"
|
||
percentage_value: !lambda "return id(tile2_percentage).state;"
|
||
position_value: !lambda "return id(tile2_cover_position).state;"
|
||
icon_text: "${TILE2_ICON}"
|
||
slider_bg_color: ${TILE2_BG_DISABLED_COLOR}
|
||
slider_fill_color: ${TILE2_CIRCLE_ACTIVE_COLOR}
|
||
slider_icon_color: ${TILE2_ICON_ACTIVE_COLOR}
|
||
label_off: "${TILE2_LABEL_OFF}"
|
||
label_on: "${TILE2_LABEL_ON}"
|
||
supported_color_modes: !lambda "return std::string(id(tile2_supported_color_modes).state.c_str());"
|
||
climate_current_temperature: !lambda "return id(tile2_climate_current_temperature).state;"
|
||
climate_target_temperature: !lambda "return id(tile2_climate_target_temperature).state;"
|
||
climate_humidity: !lambda "return id(tile2_climate_humidity).state;"
|
||
climate_mode: !lambda "return std::string(id(tile2_climate_mode).state.c_str());"
|
||
climate_supported_modes: !lambda "return std::string(id(tile2_climate_supported_modes).state.c_str());"
|
||
climate_min_temp: "${TILE2_CLIMATE_MIN_TEMP}"
|
||
climate_max_temp: "${TILE2_CLIMATE_MAX_TEMP}"
|
||
climate_step: "${TILE2_CLIMATE_STEP}"
|
||
# Long-press modus "vacuum": opent de stofzuigerbediening.
|
||
# Valt buiten de action/slider-keten, dus die blijft ongewijzigd.
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE2_LONGPRESS}\") == \"vacuum\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_vacuum_overlay
|
||
title: "${TILE2_TITLE}"
|
||
entity: "${TILE2_ENTITY}"
|
||
|
||
# ---------- TILE 3 ----------
|
||
- obj:
|
||
id: tile3
|
||
x: ${GRID_COL1_X}
|
||
y: ${GRID_ROW2_Y}
|
||
width: ${TILE_W}
|
||
height: ${TILE_H}
|
||
styles: style_tile
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
# Directe lokale feedback: LVGL past dit toe zodra de vinger het
|
||
# glas raakt, in dezelfde frame. Onafhankelijk van Home Assistant.
|
||
pressed:
|
||
bg_opa: 45%
|
||
widgets:
|
||
- obj:
|
||
id: tile3_icon_circle
|
||
x: ${TILE_ICON_X}
|
||
y: ${TILE_ICON_Y}
|
||
width: 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 !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 3);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile3_press" }
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: "${TILE3_TYPE}"
|
||
tap_action: "${TILE3_TAP_ACTION}"
|
||
service: "${TILE3_TAP_SERVICE}"
|
||
entity_id: "${TILE3_ENTITY}"
|
||
param_key: "${TILE3_TAP_PARAM_KEY}"
|
||
param_val: "${TILE3_TAP_PARAM_VAL}"
|
||
was_on: !lambda |-
|
||
if (std::string("${TILE3_TYPE}") == "cover")
|
||
return id(tile3_cover_state).state != "closed";
|
||
return std::string(id(ha_state_tile3).state.c_str()) == "on";
|
||
on_long_press:
|
||
- if:
|
||
condition:
|
||
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 3);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile3_long_press" }
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE3_LONGPRESS}\") == \"action\";"
|
||
then:
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: !lambda |-
|
||
std::string t = "${TILE3_LONGPRESS_ACTION_TYPE}";
|
||
return t.empty() ? std::string("${TILE3_TYPE}") : t;
|
||
tap_action: "activate"
|
||
service: "${TILE3_LONGPRESS_ACTION_SERVICE}"
|
||
entity_id: "${TILE3_LONGPRESS_ACTION}"
|
||
param_key: ""
|
||
param_val: ""
|
||
was_on: false
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE3_LONGPRESS}\") == \"slider\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_value_overlay
|
||
title: "${TILE3_TITLE}"
|
||
entity: "${TILE3_ENTITY}"
|
||
tile_type: "${TILE3_TYPE}"
|
||
longpress_mode: "${TILE3_LONGPRESS_SLIDER}"
|
||
was_on: !lambda |-
|
||
if (std::string("${TILE3_TYPE}") == "cover")
|
||
return id(tile3_cover_state).state != "closed";
|
||
return std::string(id(ha_state_tile3).state.c_str()) == "on";
|
||
off_value: !lambda "return atoi(\"${TILE3_LONGPRESS_OFF_VALUE}\");"
|
||
brightness_value: !lambda "return id(tile3_brightness).state;"
|
||
percentage_value: !lambda "return id(tile3_percentage).state;"
|
||
position_value: !lambda "return id(tile3_cover_position).state;"
|
||
icon_text: "${TILE3_ICON}"
|
||
slider_bg_color: ${TILE3_BG_DISABLED_COLOR}
|
||
slider_fill_color: ${TILE3_CIRCLE_ACTIVE_COLOR}
|
||
slider_icon_color: ${TILE3_ICON_ACTIVE_COLOR}
|
||
label_off: "${TILE3_LABEL_OFF}"
|
||
label_on: "${TILE3_LABEL_ON}"
|
||
supported_color_modes: !lambda "return std::string(id(tile3_supported_color_modes).state.c_str());"
|
||
climate_current_temperature: !lambda "return id(tile3_climate_current_temperature).state;"
|
||
climate_target_temperature: !lambda "return id(tile3_climate_target_temperature).state;"
|
||
climate_humidity: !lambda "return id(tile3_climate_humidity).state;"
|
||
climate_mode: !lambda "return std::string(id(tile3_climate_mode).state.c_str());"
|
||
climate_supported_modes: !lambda "return std::string(id(tile3_climate_supported_modes).state.c_str());"
|
||
climate_min_temp: "${TILE3_CLIMATE_MIN_TEMP}"
|
||
climate_max_temp: "${TILE3_CLIMATE_MAX_TEMP}"
|
||
climate_step: "${TILE3_CLIMATE_STEP}"
|
||
# Long-press modus "vacuum": opent de stofzuigerbediening.
|
||
# Valt buiten de action/slider-keten, dus die blijft ongewijzigd.
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE3_LONGPRESS}\") == \"vacuum\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_vacuum_overlay
|
||
title: "${TILE3_TITLE}"
|
||
entity: "${TILE3_ENTITY}"
|
||
|
||
# ---------- TILE 4 ----------
|
||
- obj:
|
||
id: tile4
|
||
x: ${GRID_COL2_X}
|
||
y: ${GRID_ROW2_Y}
|
||
width: ${TILE_W}
|
||
height: ${TILE_H}
|
||
styles: style_tile
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
# Directe lokale feedback: LVGL past dit toe zodra de vinger het
|
||
# glas raakt, in dezelfde frame. Onafhankelijk van Home Assistant.
|
||
pressed:
|
||
bg_opa: 45%
|
||
widgets:
|
||
- obj:
|
||
id: tile4_icon_circle
|
||
x: ${TILE_ICON_X}
|
||
y: ${TILE_ICON_Y}
|
||
width: 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 !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 4);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile4_press" }
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: "${TILE4_TYPE}"
|
||
tap_action: "${TILE4_TAP_ACTION}"
|
||
service: "${TILE4_TAP_SERVICE}"
|
||
entity_id: "${TILE4_ENTITY}"
|
||
param_key: "${TILE4_TAP_PARAM_KEY}"
|
||
param_val: "${TILE4_TAP_PARAM_VAL}"
|
||
was_on: !lambda |-
|
||
if (std::string("${TILE4_TYPE}") == "cover")
|
||
return id(tile4_cover_state).state != "closed";
|
||
return std::string(id(ha_state_tile4).state.c_str()) == "on";
|
||
on_long_press:
|
||
- if:
|
||
condition:
|
||
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 4);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile4_long_press" }
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE4_LONGPRESS}\") == \"action\";"
|
||
then:
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: !lambda |-
|
||
std::string t = "${TILE4_LONGPRESS_ACTION_TYPE}";
|
||
return t.empty() ? std::string("${TILE4_TYPE}") : t;
|
||
tap_action: "activate"
|
||
service: "${TILE4_LONGPRESS_ACTION_SERVICE}"
|
||
entity_id: "${TILE4_LONGPRESS_ACTION}"
|
||
param_key: ""
|
||
param_val: ""
|
||
was_on: false
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE4_LONGPRESS}\") == \"slider\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_value_overlay
|
||
title: "${TILE4_TITLE}"
|
||
entity: "${TILE4_ENTITY}"
|
||
tile_type: "${TILE4_TYPE}"
|
||
longpress_mode: "${TILE4_LONGPRESS_SLIDER}"
|
||
was_on: !lambda |-
|
||
if (std::string("${TILE4_TYPE}") == "cover")
|
||
return id(tile4_cover_state).state != "closed";
|
||
return std::string(id(ha_state_tile4).state.c_str()) == "on";
|
||
off_value: !lambda "return atoi(\"${TILE4_LONGPRESS_OFF_VALUE}\");"
|
||
brightness_value: !lambda "return id(tile4_brightness).state;"
|
||
percentage_value: !lambda "return id(tile4_percentage).state;"
|
||
position_value: !lambda "return id(tile4_cover_position).state;"
|
||
icon_text: "${TILE4_ICON}"
|
||
slider_bg_color: ${TILE4_BG_DISABLED_COLOR}
|
||
slider_fill_color: ${TILE4_CIRCLE_ACTIVE_COLOR}
|
||
slider_icon_color: ${TILE4_ICON_ACTIVE_COLOR}
|
||
label_off: "${TILE4_LABEL_OFF}"
|
||
label_on: "${TILE4_LABEL_ON}"
|
||
supported_color_modes: !lambda "return std::string(id(tile4_supported_color_modes).state.c_str());"
|
||
climate_current_temperature: !lambda "return id(tile4_climate_current_temperature).state;"
|
||
climate_target_temperature: !lambda "return id(tile4_climate_target_temperature).state;"
|
||
climate_humidity: !lambda "return id(tile4_climate_humidity).state;"
|
||
climate_mode: !lambda "return std::string(id(tile4_climate_mode).state.c_str());"
|
||
climate_supported_modes: !lambda "return std::string(id(tile4_climate_supported_modes).state.c_str());"
|
||
climate_min_temp: "${TILE4_CLIMATE_MIN_TEMP}"
|
||
climate_max_temp: "${TILE4_CLIMATE_MAX_TEMP}"
|
||
climate_step: "${TILE4_CLIMATE_STEP}"
|
||
# Long-press modus "vacuum": opent de stofzuigerbediening.
|
||
# Valt buiten de action/slider-keten, dus die blijft ongewijzigd.
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE4_LONGPRESS}\") == \"vacuum\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_vacuum_overlay
|
||
title: "${TILE4_TITLE}"
|
||
entity: "${TILE4_ENTITY}"
|
||
|
||
# ---------- TILE 5 ----------
|
||
- obj:
|
||
id: tile5
|
||
x: ${GRID_COL1_X}
|
||
y: ${GRID_ROW3_Y}
|
||
width: ${TILE_W}
|
||
height: ${TILE_H}
|
||
styles: style_tile
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
# Directe lokale feedback: LVGL past dit toe zodra de vinger het
|
||
# glas raakt, in dezelfde frame. Onafhankelijk van Home Assistant.
|
||
pressed:
|
||
bg_opa: 45%
|
||
widgets:
|
||
- obj:
|
||
id: tile5_icon_circle
|
||
x: ${TILE_ICON_X}
|
||
y: ${TILE_ICON_Y}
|
||
width: 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 !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 5);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile5_press" }
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: "${TILE5_TYPE}"
|
||
tap_action: "${TILE5_TAP_ACTION}"
|
||
service: "${TILE5_TAP_SERVICE}"
|
||
entity_id: "${TILE5_ENTITY}"
|
||
param_key: "${TILE5_TAP_PARAM_KEY}"
|
||
param_val: "${TILE5_TAP_PARAM_VAL}"
|
||
was_on: !lambda |-
|
||
if (std::string("${TILE5_TYPE}") == "cover")
|
||
return id(tile5_cover_state).state != "closed";
|
||
return std::string(id(ha_state_tile5).state.c_str()) == "on";
|
||
on_long_press:
|
||
- if:
|
||
condition:
|
||
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 5);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile5_long_press" }
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE5_LONGPRESS}\") == \"action\";"
|
||
then:
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: !lambda |-
|
||
std::string t = "${TILE5_LONGPRESS_ACTION_TYPE}";
|
||
return t.empty() ? std::string("${TILE5_TYPE}") : t;
|
||
tap_action: "activate"
|
||
service: "${TILE5_LONGPRESS_ACTION_SERVICE}"
|
||
entity_id: "${TILE5_LONGPRESS_ACTION}"
|
||
param_key: ""
|
||
param_val: ""
|
||
was_on: false
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE5_LONGPRESS}\") == \"slider\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_value_overlay
|
||
title: "${TILE5_TITLE}"
|
||
entity: "${TILE5_ENTITY}"
|
||
tile_type: "${TILE5_TYPE}"
|
||
longpress_mode: "${TILE5_LONGPRESS_SLIDER}"
|
||
was_on: !lambda |-
|
||
if (std::string("${TILE5_TYPE}") == "cover")
|
||
return id(tile5_cover_state).state != "closed";
|
||
return std::string(id(ha_state_tile5).state.c_str()) == "on";
|
||
off_value: !lambda "return atoi(\"${TILE5_LONGPRESS_OFF_VALUE}\");"
|
||
brightness_value: !lambda "return id(tile5_brightness).state;"
|
||
percentage_value: !lambda "return id(tile5_percentage).state;"
|
||
position_value: !lambda "return id(tile5_cover_position).state;"
|
||
icon_text: "${TILE5_ICON}"
|
||
slider_bg_color: ${TILE5_BG_DISABLED_COLOR}
|
||
slider_fill_color: ${TILE5_CIRCLE_ACTIVE_COLOR}
|
||
slider_icon_color: ${TILE5_ICON_ACTIVE_COLOR}
|
||
label_off: "${TILE5_LABEL_OFF}"
|
||
label_on: "${TILE5_LABEL_ON}"
|
||
supported_color_modes: !lambda "return std::string(id(tile5_supported_color_modes).state.c_str());"
|
||
climate_current_temperature: !lambda "return id(tile5_climate_current_temperature).state;"
|
||
climate_target_temperature: !lambda "return id(tile5_climate_target_temperature).state;"
|
||
climate_humidity: !lambda "return id(tile5_climate_humidity).state;"
|
||
climate_mode: !lambda "return std::string(id(tile5_climate_mode).state.c_str());"
|
||
climate_supported_modes: !lambda "return std::string(id(tile5_climate_supported_modes).state.c_str());"
|
||
climate_min_temp: "${TILE5_CLIMATE_MIN_TEMP}"
|
||
climate_max_temp: "${TILE5_CLIMATE_MAX_TEMP}"
|
||
climate_step: "${TILE5_CLIMATE_STEP}"
|
||
# Long-press modus "vacuum": opent de stofzuigerbediening.
|
||
# Valt buiten de action/slider-keten, dus die blijft ongewijzigd.
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE5_LONGPRESS}\") == \"vacuum\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_vacuum_overlay
|
||
title: "${TILE5_TITLE}"
|
||
entity: "${TILE5_ENTITY}"
|
||
|
||
# ---------- TILE 6 ----------
|
||
- obj:
|
||
id: tile6
|
||
x: ${GRID_COL2_X}
|
||
y: ${GRID_ROW3_Y}
|
||
width: ${TILE_W}
|
||
height: ${TILE_H}
|
||
styles: style_tile
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
# Directe lokale feedback: LVGL past dit toe zodra de vinger het
|
||
# glas raakt, in dezelfde frame. Onafhankelijk van Home Assistant.
|
||
pressed:
|
||
bg_opa: 45%
|
||
widgets:
|
||
- obj:
|
||
id: tile6_icon_circle
|
||
x: ${TILE_ICON_X}
|
||
y: ${TILE_ICON_Y}
|
||
width: 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 !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 6);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile6_press" }
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: "${TILE6_TYPE}"
|
||
tap_action: "${TILE6_TAP_ACTION}"
|
||
service: "${TILE6_TAP_SERVICE}"
|
||
entity_id: "${TILE6_ENTITY}"
|
||
param_key: "${TILE6_TAP_PARAM_KEY}"
|
||
param_val: "${TILE6_TAP_PARAM_VAL}"
|
||
was_on: !lambda |-
|
||
if (std::string("${TILE6_TYPE}") == "cover")
|
||
return id(tile6_cover_state).state != "closed";
|
||
return std::string(id(ha_state_tile6).state.c_str()) == "on";
|
||
on_long_press:
|
||
- if:
|
||
condition:
|
||
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 6);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile6_long_press" }
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE6_LONGPRESS}\") == \"action\";"
|
||
then:
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: !lambda |-
|
||
std::string t = "${TILE6_LONGPRESS_ACTION_TYPE}";
|
||
return t.empty() ? std::string("${TILE6_TYPE}") : t;
|
||
tap_action: "activate"
|
||
service: "${TILE6_LONGPRESS_ACTION_SERVICE}"
|
||
entity_id: "${TILE6_LONGPRESS_ACTION}"
|
||
param_key: ""
|
||
param_val: ""
|
||
was_on: false
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE6_LONGPRESS}\") == \"slider\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_value_overlay
|
||
title: "${TILE6_TITLE}"
|
||
entity: "${TILE6_ENTITY}"
|
||
tile_type: "${TILE6_TYPE}"
|
||
longpress_mode: "${TILE6_LONGPRESS_SLIDER}"
|
||
was_on: !lambda |-
|
||
if (std::string("${TILE6_TYPE}") == "cover")
|
||
return id(tile6_cover_state).state != "closed";
|
||
return std::string(id(ha_state_tile6).state.c_str()) == "on";
|
||
off_value: !lambda "return atoi(\"${TILE6_LONGPRESS_OFF_VALUE}\");"
|
||
brightness_value: !lambda "return id(tile6_brightness).state;"
|
||
percentage_value: !lambda "return id(tile6_percentage).state;"
|
||
position_value: !lambda "return id(tile6_cover_position).state;"
|
||
icon_text: "${TILE6_ICON}"
|
||
slider_bg_color: ${TILE6_BG_DISABLED_COLOR}
|
||
slider_fill_color: ${TILE6_CIRCLE_ACTIVE_COLOR}
|
||
slider_icon_color: ${TILE6_ICON_ACTIVE_COLOR}
|
||
label_off: "${TILE6_LABEL_OFF}"
|
||
label_on: "${TILE6_LABEL_ON}"
|
||
supported_color_modes: !lambda "return std::string(id(tile6_supported_color_modes).state.c_str());"
|
||
climate_current_temperature: !lambda "return id(tile6_climate_current_temperature).state;"
|
||
climate_target_temperature: !lambda "return id(tile6_climate_target_temperature).state;"
|
||
climate_humidity: !lambda "return id(tile6_climate_humidity).state;"
|
||
climate_mode: !lambda "return std::string(id(tile6_climate_mode).state.c_str());"
|
||
climate_supported_modes: !lambda "return std::string(id(tile6_climate_supported_modes).state.c_str());"
|
||
climate_min_temp: "${TILE6_CLIMATE_MIN_TEMP}"
|
||
climate_max_temp: "${TILE6_CLIMATE_MAX_TEMP}"
|
||
climate_step: "${TILE6_CLIMATE_STEP}"
|
||
# Long-press modus "vacuum": opent de stofzuigerbediening.
|
||
# Valt buiten de action/slider-keten, dus die blijft ongewijzigd.
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE6_LONGPRESS}\") == \"vacuum\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_vacuum_overlay
|
||
title: "${TILE6_TITLE}"
|
||
entity: "${TILE6_ENTITY}"
|
||
|
||
# ---------- TILE 7 — Tafellamp ----------
|
||
# Alleen-helderheid lamp: er zijn bewust geen percentage/cover/
|
||
# climate-sensors voor deze tegel aangemaakt, die zouden alleen
|
||
# maar HA-subscriptions en heap kosten. De overlay krijgt daarom
|
||
# NAN / "" voor de velden die een light nooit gebruikt.
|
||
- obj:
|
||
id: tile7
|
||
hidden: true
|
||
x: ${GRID_COL1_X}
|
||
y: ${GRID_ROW4_Y}
|
||
width: ${TILE_W}
|
||
height: ${TILE_H}
|
||
styles: style_tile
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
pressed:
|
||
bg_opa: 45%
|
||
widgets:
|
||
- obj:
|
||
id: tile7_icon_circle
|
||
x: ${TILE_ICON_X}
|
||
y: ${TILE_ICON_Y}
|
||
width: 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 !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 7);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile7_press" }
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: "${TILE7_TYPE}"
|
||
tap_action: "${TILE7_TAP_ACTION}"
|
||
service: "${TILE7_TAP_SERVICE}"
|
||
entity_id: "${TILE7_ENTITY}"
|
||
param_key: "${TILE7_TAP_PARAM_KEY}"
|
||
param_val: "${TILE7_TAP_PARAM_VAL}"
|
||
was_on: !lambda "return std::string(id(ha_state_tile7).state.c_str()) == \"on\";"
|
||
on_long_press:
|
||
- if:
|
||
condition:
|
||
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 7);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile7_long_press" }
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE7_LONGPRESS}\") == \"action\";"
|
||
then:
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: !lambda |-
|
||
std::string t = "${TILE7_LONGPRESS_ACTION_TYPE}";
|
||
return t.empty() ? std::string("${TILE7_TYPE}") : t;
|
||
tap_action: "activate"
|
||
service: "${TILE7_LONGPRESS_ACTION_SERVICE}"
|
||
entity_id: "${TILE7_LONGPRESS_ACTION}"
|
||
param_key: ""
|
||
param_val: ""
|
||
was_on: false
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE7_LONGPRESS}\") == \"slider\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_value_overlay
|
||
title: "${TILE7_TITLE}"
|
||
entity: "${TILE7_ENTITY}"
|
||
tile_type: "${TILE7_TYPE}"
|
||
longpress_mode: "${TILE7_LONGPRESS_SLIDER}"
|
||
was_on: !lambda "return std::string(id(ha_state_tile7).state.c_str()) == \"on\";"
|
||
off_value: !lambda "return atoi(\"${TILE7_LONGPRESS_OFF_VALUE}\");"
|
||
brightness_value: !lambda "return id(tile7_brightness).state;"
|
||
percentage_value: !lambda "return NAN;"
|
||
position_value: !lambda "return NAN;"
|
||
icon_text: "${TILE7_ICON}"
|
||
slider_bg_color: ${TILE7_BG_DISABLED_COLOR}
|
||
slider_fill_color: ${TILE7_CIRCLE_ACTIVE_COLOR}
|
||
slider_icon_color: ${TILE7_ICON_ACTIVE_COLOR}
|
||
label_off: "${TILE7_LABEL_OFF}"
|
||
label_on: "${TILE7_LABEL_ON}"
|
||
supported_color_modes: !lambda "return std::string(id(tile7_supported_color_modes).state.c_str());"
|
||
climate_current_temperature: !lambda "return NAN;"
|
||
climate_target_temperature: !lambda "return NAN;"
|
||
climate_humidity: !lambda "return NAN;"
|
||
climate_mode: ""
|
||
climate_supported_modes: ""
|
||
climate_min_temp: "5.0"
|
||
climate_max_temp: "30.0"
|
||
climate_step: "0.5"
|
||
|
||
# ---------- TILE 8 — Brievenbus reset ----------
|
||
# input_select met opties Vol/Leeg. De tegel licht op zodra de
|
||
# status TILE8_ACTIVE_STATE ("Vol") is; een korte tik stuurt
|
||
# input_select.select_option met option: "Leeg".
|
||
# Geen slider-modus, dus deze tegel heeft alleen ha_state_tile8
|
||
# nodig — verder geen enkele HA-subscription.
|
||
- obj:
|
||
id: tile8
|
||
hidden: true
|
||
x: ${GRID_COL2_X}
|
||
y: ${GRID_ROW4_Y}
|
||
width: ${TILE_W}
|
||
height: ${TILE_H}
|
||
styles: style_tile
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
pressed:
|
||
bg_opa: 45%
|
||
widgets:
|
||
- obj:
|
||
id: tile8_icon_circle
|
||
x: ${TILE_ICON_X}
|
||
y: ${TILE_ICON_Y}
|
||
width: 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 !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 8);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile8_press" }
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: "${TILE8_TYPE}"
|
||
tap_action: "${TILE8_TAP_ACTION}"
|
||
service: "${TILE8_TAP_SERVICE}"
|
||
entity_id: "${TILE8_ENTITY}"
|
||
param_key: "${TILE8_TAP_PARAM_KEY}"
|
||
param_val: "${TILE8_TAP_PARAM_VAL}"
|
||
was_on: !lambda "return std::string(id(ha_state_tile8).state.c_str()) == \"${TILE8_ACTIVE_STATE}\";"
|
||
on_long_press:
|
||
- if:
|
||
condition:
|
||
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 8);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile8_long_press" }
|
||
# Slider-modus kan hier niet zonder eerst de bijbehorende
|
||
# sensors aan te maken; "action" werkt wel out of the box.
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE8_LONGPRESS}\") == \"action\";"
|
||
then:
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: !lambda |-
|
||
std::string t = "${TILE8_LONGPRESS_ACTION_TYPE}";
|
||
return t.empty() ? std::string("${TILE8_TYPE}") : t;
|
||
tap_action: "activate"
|
||
service: "${TILE8_LONGPRESS_ACTION_SERVICE}"
|
||
entity_id: "${TILE8_LONGPRESS_ACTION}"
|
||
param_key: ""
|
||
param_val: ""
|
||
was_on: false
|
||
|
||
# ---------- TILE 9 — Woonkamer (verhuisd van tegel 2) ----------
|
||
- obj:
|
||
id: tile9
|
||
hidden: true
|
||
x: ${GRID_COL1_X}
|
||
y: ${GRID_ROW5_Y}
|
||
width: ${TILE_W}
|
||
height: ${TILE_H}
|
||
styles: style_tile
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
pressed:
|
||
bg_opa: 45%
|
||
widgets:
|
||
- obj:
|
||
id: tile9_icon_circle
|
||
x: ${TILE_ICON_X}
|
||
y: ${TILE_ICON_Y}
|
||
width: 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 !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 9);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile9_press" }
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: "${TILE9_TYPE}"
|
||
tap_action: "${TILE9_TAP_ACTION}"
|
||
service: "${TILE9_TAP_SERVICE}"
|
||
entity_id: "${TILE9_ENTITY}"
|
||
param_key: "${TILE9_TAP_PARAM_KEY}"
|
||
param_val: "${TILE9_TAP_PARAM_VAL}"
|
||
was_on: !lambda "return std::string(id(ha_state_tile9).state.c_str()) == \"on\";"
|
||
on_long_press:
|
||
- if:
|
||
condition:
|
||
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 9);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile9_long_press" }
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE9_LONGPRESS}\") == \"action\";"
|
||
then:
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: !lambda |-
|
||
std::string t = "${TILE9_LONGPRESS_ACTION_TYPE}";
|
||
return t.empty() ? std::string("${TILE9_TYPE}") : t;
|
||
tap_action: "activate"
|
||
service: "${TILE9_LONGPRESS_ACTION_SERVICE}"
|
||
entity_id: "${TILE9_LONGPRESS_ACTION}"
|
||
param_key: ""
|
||
param_val: ""
|
||
was_on: false
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE9_LONGPRESS}\") == \"slider\";"
|
||
then:
|
||
- script.execute:
|
||
id: open_value_overlay
|
||
title: "${TILE9_TITLE}"
|
||
entity: "${TILE9_ENTITY}"
|
||
tile_type: "${TILE9_TYPE}"
|
||
longpress_mode: "${TILE9_LONGPRESS_SLIDER}"
|
||
was_on: !lambda "return std::string(id(ha_state_tile9).state.c_str()) == \"on\";"
|
||
off_value: !lambda "return atoi(\"${TILE9_LONGPRESS_OFF_VALUE}\");"
|
||
brightness_value: !lambda "return id(tile9_brightness).state;"
|
||
percentage_value: !lambda "return NAN;"
|
||
position_value: !lambda "return NAN;"
|
||
icon_text: "${TILE9_ICON}"
|
||
slider_bg_color: ${TILE9_BG_DISABLED_COLOR}
|
||
slider_fill_color: ${TILE9_CIRCLE_ACTIVE_COLOR}
|
||
slider_icon_color: ${TILE9_ICON_ACTIVE_COLOR}
|
||
label_off: "${TILE9_LABEL_OFF}"
|
||
label_on: "${TILE9_LABEL_ON}"
|
||
supported_color_modes: !lambda "return std::string(id(tile9_supported_color_modes).state.c_str());"
|
||
climate_current_temperature: !lambda "return NAN;"
|
||
climate_target_temperature: !lambda "return NAN;"
|
||
climate_humidity: !lambda "return NAN;"
|
||
climate_mode: ""
|
||
climate_supported_modes: ""
|
||
climate_min_temp: "5.0"
|
||
climate_max_temp: "30.0"
|
||
climate_step: "0.5"
|
||
|
||
# ---------- TILE 10 — Alarm aan/uit ----------
|
||
- obj:
|
||
id: tile10
|
||
hidden: true
|
||
x: ${GRID_COL2_X}
|
||
y: ${GRID_ROW5_Y}
|
||
width: ${TILE_W}
|
||
height: ${TILE_H}
|
||
styles: style_tile
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
pressed:
|
||
bg_opa: 45%
|
||
widgets:
|
||
- obj:
|
||
id: tile10_icon_circle
|
||
x: ${TILE_ICON_X}
|
||
y: ${TILE_ICON_Y}
|
||
width: 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 !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 10);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile10_press" }
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: "${TILE10_TYPE}"
|
||
tap_action: "${TILE10_TAP_ACTION}"
|
||
service: "${TILE10_TAP_SERVICE}"
|
||
entity_id: "${TILE10_ENTITY}"
|
||
param_key: "${TILE10_TAP_PARAM_KEY}"
|
||
param_val: "${TILE10_TAP_PARAM_VAL}"
|
||
was_on: !lambda "return std::string(id(ha_state_tile10).state.c_str()) == \"on\";"
|
||
on_long_press:
|
||
- if:
|
||
condition:
|
||
lambda: 'return !runtime_tiles::enabled && cyd::touch_guard.accept(millis(), 10);'
|
||
then:
|
||
- script.execute: { id: publish_action, action: "tile10_long_press" }
|
||
- if:
|
||
condition:
|
||
lambda: "return std::string(\"${TILE10_LONGPRESS}\") == \"action\";"
|
||
then:
|
||
- script.execute:
|
||
id: do_tile_action
|
||
tile_type: !lambda |-
|
||
std::string t = "${TILE10_LONGPRESS_ACTION_TYPE}";
|
||
return t.empty() ? std::string("${TILE10_TYPE}") : t;
|
||
tap_action: "activate"
|
||
service: "${TILE10_LONGPRESS_ACTION_SERVICE}"
|
||
entity_id: "${TILE10_LONGPRESS_ACTION}"
|
||
param_key: ""
|
||
param_val: ""
|
||
was_on: false
|
||
|
||
# ---------- VALUE SLIDER OVERLAY ----------
|
||
# ---------- PAGE NAVIGATION: one full-width bar ----------
|
||
- obj:
|
||
id: page_prev
|
||
align: BOTTOM_LEFT
|
||
x: 0
|
||
y: 0
|
||
width: 130
|
||
height: 32
|
||
pad_all: 0
|
||
radius: 0
|
||
bg_opa: TRANSP
|
||
border_width: 0
|
||
scrollable: false
|
||
pressed:
|
||
bg_color: 0xE3E3E3
|
||
bg_opa: COVER
|
||
on_short_click:
|
||
- if:
|
||
condition:
|
||
lambda: 'return cyd::touch_guard.accept(millis(), 11);'
|
||
then:
|
||
- lambda: 'id(tile_page) = std::max(0, id(tile_page) - 1);'
|
||
- script.execute: show_tile_page
|
||
widgets:
|
||
- label:
|
||
align: LEFT_MID
|
||
x: 12
|
||
text: "< Vorige"
|
||
text_font: label
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- label:
|
||
id: page_number
|
||
align: BOTTOM_MID
|
||
y: 0
|
||
width: 60
|
||
height: 32
|
||
pad_top: 9
|
||
border_width: 0
|
||
text: "1 / 2"
|
||
text_align: CENTER
|
||
text_font: sublabel
|
||
text_color: 0x1B1B1B
|
||
- obj:
|
||
id: page_next
|
||
align: BOTTOM_RIGHT
|
||
x: 0
|
||
y: 0
|
||
width: 130
|
||
height: 32
|
||
pad_all: 0
|
||
radius: 0
|
||
bg_opa: TRANSP
|
||
border_width: 0
|
||
scrollable: false
|
||
pressed:
|
||
bg_color: 0xE3E3E3
|
||
bg_opa: COVER
|
||
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: RIGHT_MID
|
||
x: -12
|
||
text: "Volgende >"
|
||
text_font: label
|
||
text_color: 0x1B1B1B
|
||
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:
|
||
- script.execute: close_cards
|
||
|
||
- obj:
|
||
id: brightness_overlay_dim
|
||
x: 0
|
||
y: 0
|
||
width: ${DISPLAY_W}
|
||
height: ${DISPLAY_H}
|
||
bg_color: 0xFFFFFF
|
||
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: 0xFFFFFF
|
||
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: 0xD5EEFC
|
||
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: 0x1B1B1B
|
||
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: 0xF7F7F7
|
||
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: 0xFFFFFF
|
||
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: 0xEEEEEE
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xFFFFFF
|
||
border_opa: 20%
|
||
clickable: true
|
||
scrollable: false
|
||
on_click:
|
||
- script.execute: close_cards
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "x"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
|
||
- label:
|
||
id: color_detail_title
|
||
width: 210
|
||
long_mode: DOT
|
||
text_align: CENTER
|
||
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: 0xEEEEEE
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
on_click:
|
||
- script.execute: close_cards
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "\U000F012C"
|
||
text_font: materialdesign_icons
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
|
||
# ---------- CLIMATE DETAIL OVERLAY ----------
|
||
- obj:
|
||
id: climate_detail_overlay
|
||
x: 0
|
||
y: 0
|
||
width: ${DISPLAY_W}
|
||
height: ${DISPLAY_H}
|
||
bg_color: 0xF7F7F7
|
||
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: 0xFFFFFF
|
||
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: 0xEEEEEE
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xFFFFFF
|
||
border_opa: 20%
|
||
clickable: true
|
||
scrollable: false
|
||
on_click:
|
||
- script.execute: close_cards
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "x"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
|
||
- obj:
|
||
id: climate_power_button
|
||
align: TOP_RIGHT
|
||
x: -10
|
||
y: 9
|
||
width: 44
|
||
height: 44
|
||
radius: 999
|
||
bg_color: 0xEEEEEE
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
on_click:
|
||
- script.execute: climate_power_toggle
|
||
widgets:
|
||
- label:
|
||
id: climate_power_icon
|
||
align: CENTER
|
||
text: "\U000F0425"
|
||
text_font: materialdesign_icons_mini
|
||
text_color: 0x1B1B1B
|
||
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: "Nu: --"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
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: 0x111111
|
||
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: "Gewenst"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
text_opa: 68%
|
||
clickable: false
|
||
|
||
- label:
|
||
id: climate_target_label
|
||
align: CENTER
|
||
y: 20
|
||
text: "20.0°"
|
||
text_font: headline
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
|
||
- obj:
|
||
id: climate_minus_button
|
||
align: BOTTOM_LEFT
|
||
x: 38
|
||
y: -16
|
||
width: 48
|
||
height: 36
|
||
radius: 18
|
||
bg_color: 0xEEEEEE
|
||
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: 0x1B1B1B
|
||
clickable: false
|
||
|
||
- obj:
|
||
id: climate_plus_button
|
||
align: BOTTOM_RIGHT
|
||
x: -38
|
||
y: -16
|
||
width: 48
|
||
height: 36
|
||
radius: 18
|
||
bg_color: 0xEEEEEE
|
||
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: 0x1B1B1B
|
||
clickable: false
|
||
|
||
- obj:
|
||
id: climate_mode_pill
|
||
align: BOTTOM_MID
|
||
y: -18
|
||
width: 126
|
||
height: 34
|
||
radius: 17
|
||
bg_color: 0xF0F0F0
|
||
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: 0x1B1B1B
|
||
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: 0xE7E7E7
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
scrollbar_mode: 'OFF'
|
||
widgets:
|
||
- label:
|
||
align: TOP_MID
|
||
y: 16
|
||
text: "Modus"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
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: 44
|
||
width: 98%
|
||
height: 178
|
||
bg_opa: TRANSP
|
||
border_width: 0
|
||
scrollable: false
|
||
clickable: false
|
||
pad_all: 3
|
||
layout:
|
||
type: FLEX
|
||
pad_row: 3
|
||
pad_column: 4
|
||
flex_flow: ROW_WRAP
|
||
flex_align_main: CENTER
|
||
flex_align_cross: CENTER
|
||
flex_align_track: CENTER
|
||
widgets:
|
||
- obj:
|
||
id: cmode_off
|
||
width: 88
|
||
height: 26
|
||
radius: 13
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_mode, mode: "off" }
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Uit"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: cmode_heat
|
||
width: 88
|
||
height: 26
|
||
radius: 13
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_mode, mode: "heat" }
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Verwarmen"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: cmode_cool
|
||
width: 88
|
||
height: 26
|
||
radius: 13
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_mode, mode: "cool" }
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Koelen"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: cmode_heat_cool
|
||
width: 88
|
||
height: 26
|
||
radius: 13
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_mode, mode: "heat_cool" }
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Verw./koelen"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: cmode_auto
|
||
width: 88
|
||
height: 26
|
||
radius: 13
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
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: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: cmode_dry
|
||
width: 88
|
||
height: 26
|
||
radius: 13
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_mode, mode: "dry" }
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Drogen"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: cmode_fan_only
|
||
width: 88
|
||
height: 26
|
||
radius: 13
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_mode, mode: "fan_only" }
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Ventileren"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
|
||
- label:
|
||
id: cfan_title
|
||
width: 100%
|
||
height: 11
|
||
text: "Ventilator"
|
||
text_font: sublabel
|
||
text_color: 0x6B6B6B
|
||
text_align: CENTER
|
||
clickable: false
|
||
- obj:
|
||
id: cfan_0
|
||
width: 62
|
||
height: 22
|
||
radius: 11
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_fan_mode, index: 0 }
|
||
widgets:
|
||
- label:
|
||
id: cfan_0_label
|
||
align: CENTER
|
||
text: "-"
|
||
text_font: sublabel
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: cfan_1
|
||
width: 62
|
||
height: 22
|
||
radius: 11
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_fan_mode, index: 1 }
|
||
widgets:
|
||
- label:
|
||
id: cfan_1_label
|
||
align: CENTER
|
||
text: "-"
|
||
text_font: sublabel
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: cfan_2
|
||
width: 62
|
||
height: 22
|
||
radius: 11
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_fan_mode, index: 2 }
|
||
widgets:
|
||
- label:
|
||
id: cfan_2_label
|
||
align: CENTER
|
||
text: "-"
|
||
text_font: sublabel
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: cfan_3
|
||
width: 62
|
||
height: 22
|
||
radius: 11
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_fan_mode, index: 3 }
|
||
widgets:
|
||
- label:
|
||
id: cfan_3_label
|
||
align: CENTER
|
||
text: "-"
|
||
text_font: sublabel
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- label:
|
||
id: cswing_title
|
||
width: 100%
|
||
height: 11
|
||
text: "Zwenken"
|
||
text_font: sublabel
|
||
text_color: 0x6B6B6B
|
||
text_align: CENTER
|
||
clickable: false
|
||
- obj:
|
||
id: cswing_0
|
||
width: 62
|
||
height: 22
|
||
radius: 11
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_swing_mode, index: 0 }
|
||
widgets:
|
||
- label:
|
||
id: cswing_0_label
|
||
align: CENTER
|
||
text: "-"
|
||
text_font: sublabel
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: cswing_1
|
||
width: 62
|
||
height: 22
|
||
radius: 11
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_swing_mode, index: 1 }
|
||
widgets:
|
||
- label:
|
||
id: cswing_1_label
|
||
align: CENTER
|
||
text: "-"
|
||
text_font: sublabel
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: cswing_2
|
||
width: 62
|
||
height: 22
|
||
radius: 11
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_swing_mode, index: 2 }
|
||
widgets:
|
||
- label:
|
||
id: cswing_2_label
|
||
align: CENTER
|
||
text: "-"
|
||
text_font: sublabel
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: cswing_3
|
||
width: 62
|
||
height: 22
|
||
radius: 11
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute: { id: set_climate_swing_mode, index: 3 }
|
||
widgets:
|
||
- label:
|
||
id: cswing_3_label
|
||
align: CENTER
|
||
text: "-"
|
||
text_font: sublabel
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: climate_mode_overlay_x
|
||
align: TOP_LEFT
|
||
x: 10
|
||
y: 9
|
||
width: 36
|
||
height: 36
|
||
radius: 999
|
||
bg_color: 0xFFFFFF
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xDDDDDD
|
||
border_opa: COVER
|
||
clickable: true
|
||
scrollable: false
|
||
on_click:
|
||
- lvgl.widget.hide: climate_mode_overlay
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "x"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
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: 0xFFFFFF
|
||
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: 0x1B1B1B
|
||
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: 0xF0F0F0
|
||
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: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: vac_pause
|
||
width: 72
|
||
height: 38
|
||
radius: 19
|
||
bg_color: 0xF0F0F0
|
||
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: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: vac_return_to_base
|
||
width: 72
|
||
height: 38
|
||
radius: 19
|
||
bg_color: 0xF0F0F0
|
||
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: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: vac_stop
|
||
width: 72
|
||
height: 38
|
||
radius: 19
|
||
bg_color: 0xF0F0F0
|
||
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: 0x1B1B1B
|
||
clickable: false
|
||
|
||
- label:
|
||
align: TOP_MID
|
||
y: 138
|
||
text: "Suction"
|
||
text_font: sublabel
|
||
text_color: 0x1B1B1B
|
||
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: 0xF0F0F0
|
||
bg_opa: 92%
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute:
|
||
id: vacuum_fan_speed
|
||
speed: !lambda 'return runtime_tiles::enabled ? runtime_tiles::vacuum_option(0) : "quiet";'
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Quiet"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: vacspd_balanced
|
||
width: 72
|
||
height: 32
|
||
radius: 19
|
||
bg_color: 0xF0F0F0
|
||
bg_opa: 92%
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute:
|
||
id: vacuum_fan_speed
|
||
speed: !lambda 'return runtime_tiles::enabled ? runtime_tiles::vacuum_option(1) : "balanced";'
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Balanced"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: vacspd_turbo
|
||
width: 72
|
||
height: 32
|
||
radius: 19
|
||
bg_color: 0xF0F0F0
|
||
bg_opa: 92%
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute:
|
||
id: vacuum_fan_speed
|
||
speed: !lambda 'return runtime_tiles::enabled ? runtime_tiles::vacuum_option(2) : "turbo";'
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Turbo"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
- obj:
|
||
id: vacspd_max
|
||
width: 72
|
||
height: 32
|
||
radius: 19
|
||
bg_color: 0xF0F0F0
|
||
bg_opa: 92%
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
pad_all: 0
|
||
on_click:
|
||
- script.execute:
|
||
id: vacuum_fan_speed
|
||
speed: !lambda 'return runtime_tiles::enabled ? runtime_tiles::vacuum_option(3) : "max";'
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "Max"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
|
||
- obj:
|
||
id: vacuum_overlay_x
|
||
align: TOP_LEFT
|
||
x: 10
|
||
y: 9
|
||
width: 36
|
||
height: 36
|
||
radius: 999
|
||
bg_color: 0xEEEEEE
|
||
bg_opa: COVER
|
||
border_width: 1
|
||
border_color: 0xFFFFFF
|
||
border_opa: 20%
|
||
clickable: true
|
||
scrollable: false
|
||
on_click:
|
||
- script.execute: close_cards
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "x"
|
||
text_font: sublabel_big
|
||
text_color: 0x1B1B1B
|
||
clickable: false
|
||
|
||
- obj:
|
||
id: vacuum_overlay_ok
|
||
align: TOP_RIGHT
|
||
x: -10
|
||
y: 9
|
||
width: 36
|
||
height: 36
|
||
radius: 999
|
||
bg_color: 0xD5EEFC
|
||
bg_opa: COVER
|
||
border_width: 0
|
||
clickable: true
|
||
scrollable: false
|
||
on_click:
|
||
- script.execute: close_cards
|
||
widgets:
|
||
- label:
|
||
align: CENTER
|
||
text: "\U000F012C"
|
||
text_font: materialdesign_icons
|
||
text_color: 0x1B1B1B
|
||
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: 'ESP_LOGI("ui_test", "Light sliders: %s", light_controls::self_test() ? "PASS" : "FAIL");'
|
||
- lambda: 'id(calibration_active) = false;'
|
||
- lvgl.page.show: home_page
|
||
- script.execute: wake_display
|
||
- repeat:
|
||
count: 10
|
||
then:
|
||
- lambda: |-
|
||
id(last_touch_ms) = millis();
|
||
static unsigned test_page=0;
|
||
id(tile_page) = runtime_tiles::enabled ? test_page++ % runtime_tiles::page_count() : 1;
|
||
- script.execute: show_tile_page
|
||
- delay: 300ms
|
||
- lambda: |-
|
||
lv_obj_update_layout(id(tile_scroll));
|
||
const int count = runtime_tiles::enabled ? runtime_tiles::model.count : std::clamp(atoi("${TILE_COUNT}"), 1, 10);
|
||
// Wide cards take two slots, so visibility follows the runtime packing.
|
||
const bool paginated = runtime_tiles::enabled ? runtime_tiles::page_count() > 1 : 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 = runtime_tiles::enabled ? i<6 && runtime_tiles::widgets[i].index<runtime_tiles::model.count : 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);
|
||
if(runtime_tiles::enabled)ok = runtime_tiles::check_tile_geometry() && ok;
|
||
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: |-
|
||
if(runtime_tiles::enabled){
|
||
runtime_tiles::show_page(id(tile_page),id(page_prev),id(page_next),id(page_number));
|
||
return;
|
||
}
|
||
lv_obj_t* tiles[] = {id(tile1), id(tile2), id(tile3), id(tile4), id(tile5),
|
||
id(tile6), id(tile7), id(tile8), id(tile9), id(tile10)};
|
||
const int rows[] = {${GRID_ROW1_Y}, ${GRID_ROW2_Y}, ${GRID_ROW3_Y}};
|
||
const int count = runtime_tiles::enabled ? runtime_tiles::model.count : std::clamp(atoi("${TILE_COUNT}"), 1, 10);
|
||
const bool paginated = count > 6;
|
||
if (!paginated) id(tile_page) = 0;
|
||
for (lv_obj_t* control : {id(page_prev), id(page_next), id(page_number)}) {
|
||
if (paginated) lv_obj_clear_flag(control, LV_OBJ_FLAG_HIDDEN);
|
||
else lv_obj_add_flag(control, LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
for (int i = 0; i < 10; ++i) {
|
||
bool visible = i < count && (id(tile_page) == 0 ? i < 6 : i >= 6);
|
||
if (visible) {
|
||
lv_obj_set_y(tiles[i], rows[(i < 6 ? i : i - 6) / 2]);
|
||
lv_obj_clear_flag(tiles[i], LV_OBJ_FLAG_HIDDEN);
|
||
} else {
|
||
lv_obj_add_flag(tiles[i], LV_OBJ_FLAG_HIDDEN);
|
||
}
|
||
}
|
||
lv_label_set_text(id(page_number), id(tile_page) == 0 ? "1 / 2" : "2 / 2");
|
||
if (id(tile_page) == 0) {
|
||
lv_obj_add_state(id(page_prev), LV_STATE_DISABLED);
|
||
lv_obj_clear_state(id(page_next), LV_STATE_DISABLED);
|
||
} else {
|
||
lv_obj_clear_state(id(page_prev), LV_STATE_DISABLED);
|
||
lv_obj_add_state(id(page_next), LV_STATE_DISABLED);
|
||
}
|
||
|
||
# Runtime changes do not count as touches and do not reset the idle timer.
|
||
- id: apply_screen_settings
|
||
mode: restart
|
||
then:
|
||
- lambda: |-
|
||
const auto &settings = screen_settings::current;
|
||
if (id(display_dimmed) && !settings.standby_enabled) {
|
||
id(display_dimmed) = false;
|
||
}
|
||
auto now = id(homeassistant_time).now();
|
||
const int minute = now.is_valid() ? now.hour * 60 + now.minute : -1;
|
||
const int level = id(display_dimmed) ? settings.dim_level(minute) : settings.brightness;
|
||
id(back_light).turn_on().set_transition_length(300).set_brightness(level / 100.0f).perform();
|
||
if (id(display_dimmed)) { lv_obj_clear_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN); lv_obj_move_foreground(id(dim_wake_overlay)); }
|
||
else lv_obj_add_flag(id(dim_wake_overlay), LV_OBJ_FLAG_HIDDEN);
|
||
if (settings.show_clock) lv_obj_clear_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN);
|
||
else lv_obj_add_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN);
|
||
if (!id(ui_refresh).is_running()) id(ui_refresh).execute();
|
||
|
||
- id: close_cards
|
||
then:
|
||
- lambda: |-
|
||
// Runtime cards and the legacy overlays close through one path; the swipe guard
|
||
// reads active_entity, so a hidden card must never leave it behind.
|
||
if (runtime_tiles::dismiss) { runtime_tiles::dismiss(); return; }
|
||
id(active_entity).clear();
|
||
- 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: 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 screen_settings::current.brightness / 100.0f;"
|
||
- lvgl.widget.hide: dim_wake_overlay
|
||
- script.execute: close_cards
|
||
|
||
- id: dim_display
|
||
mode: restart
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: 'return screen_settings::current.standby_enabled;'
|
||
then:
|
||
- lambda: |-
|
||
id(display_dimmed) = true;
|
||
id(apply_screen_settings).execute();
|
||
if (screen_settings::current.home_on_standby) {
|
||
// Runtime detail cards live above the tile grid, so paging alone cannot close them.
|
||
runtime_tiles::hide_detail();
|
||
id(active_entity).clear();
|
||
id(tile_page) = 0;
|
||
id(show_tile_page).execute();
|
||
}
|
||
|
||
# Terug naar het basisscherm bij standby. Bewust via de ESPHome
|
||
# acties en NIET met lv_obj_add_flag in een lambda: dit script
|
||
# draait vanuit een interval, dus buiten LVGL's eigen afhandeling.
|
||
- lvgl.widget.hide: brightness_overlay
|
||
- lvgl.widget.hide: color_detail_overlay
|
||
- lvgl.widget.hide: climate_detail_overlay
|
||
- lvgl.widget.hide: climate_mode_overlay
|
||
- lvgl.widget.hide: vacuum_overlay
|
||
|
||
- id: publish_action
|
||
mode: restart
|
||
parameters:
|
||
action: string
|
||
then:
|
||
- logger.log:
|
||
level: INFO
|
||
format: "publish_action: %s"
|
||
args: ["action.c_str()"]
|
||
- text_sensor.template.publish:
|
||
id: smartdisplay_action
|
||
state: !lambda 'return action;'
|
||
- delay: 500ms
|
||
- text_sensor.template.publish:
|
||
id: smartdisplay_action
|
||
state: ""
|
||
|
||
- id: climate_target_preview
|
||
parameters:
|
||
value: int
|
||
then:
|
||
- lambda: |-
|
||
id(climate_edit_ms) = millis();
|
||
id(climate_edit_pending) = true;
|
||
int min_x10 = id(active_climate_min_x10);
|
||
int max_x10 = id(active_climate_max_x10);
|
||
value = cyd::quantize_temperature(value, min_x10, max_x10,
|
||
id(active_climate_step_x10));
|
||
id(active_climate_target_x10) = value;
|
||
|
||
char buf[16];
|
||
snprintf(buf, sizeof(buf), "%.1f°", value / 10.0f);
|
||
lv_label_set_text(id(climate_target_label), buf);
|
||
lv_arc_set_value(id(climate_arc), value);
|
||
|
||
- id: climate_target_commit
|
||
parameters:
|
||
value: int
|
||
then:
|
||
- script.execute:
|
||
id: climate_target_preview
|
||
value: !lambda "return value;"
|
||
- if:
|
||
condition:
|
||
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true";'
|
||
then:
|
||
- homeassistant.action:
|
||
action: climate.set_temperature
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
temperature: !lambda "return id(active_climate_target_x10) / 10.0f;"
|
||
|
||
- id: climate_adjust_target
|
||
parameters:
|
||
direction: int
|
||
then:
|
||
- lambda: |-
|
||
int step_x10 = id(active_climate_step_x10);
|
||
if (step_x10 < 5) step_x10 = 5;
|
||
int next = id(active_climate_target_x10) + (direction * step_x10);
|
||
if (next < id(active_climate_min_x10)) next = id(active_climate_min_x10);
|
||
if (next > id(active_climate_max_x10)) next = id(active_climate_max_x10);
|
||
id(active_climate_target_x10) = next;
|
||
- script.execute:
|
||
id: climate_target_commit
|
||
value: !lambda "return id(active_climate_target_x10);"
|
||
|
||
# Opent de moduskiezer. Verbergt modi die de entiteit niet ondersteunt en
|
||
# markeert de huidige, zodat je ziet waar je staat voor je iets kiest.
|
||
# Opent de stofzuigerbediening en onthoudt om welke entiteit het gaat.
|
||
- id: open_vacuum_overlay
|
||
mode: restart
|
||
parameters:
|
||
title: string
|
||
entity: string
|
||
then:
|
||
- lambda: |-
|
||
id(active_entity) = entity;
|
||
lv_label_set_text(id(vacuum_title), title.c_str());
|
||
- lvgl.widget.show: vacuum_overlay
|
||
|
||
# Een losse opdracht: start, pause, stop of return_to_base.
|
||
- id: vacuum_action
|
||
mode: queued
|
||
parameters:
|
||
service: string
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true";'
|
||
then:
|
||
- homeassistant.action:
|
||
action: !lambda "return service;"
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
|
||
# Zuigkracht is een aparte as, los van start/stop.
|
||
- id: vacuum_fan_speed
|
||
mode: queued
|
||
parameters:
|
||
speed: string
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true";'
|
||
then:
|
||
- homeassistant.action:
|
||
action: vacuum.set_fan_speed
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
fan_speed: !lambda "return speed;"
|
||
|
||
- id: open_climate_mode_picker
|
||
mode: restart
|
||
then:
|
||
- lambda: |-
|
||
auto lower = [](std::string v) {
|
||
for (char &c : v) { if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a'); }
|
||
return v;
|
||
};
|
||
std::string supported = lower(id(active_climate_supported_modes));
|
||
std::string current = lower(id(active_climate_mode));
|
||
|
||
auto has = [&](const char* m) {
|
||
std::string mode(m);
|
||
if (supported.empty() || supported == "unknown" || supported == "unavailable") return false;
|
||
return supported.find("'" + mode + "'") != std::string::npos ||
|
||
supported.find("\"" + mode + "\"") != std::string::npos;
|
||
};
|
||
auto apply = [&](lv_obj_t* btn, const char* m) {
|
||
if (has(m)) lv_obj_clear_flag(btn, LV_OBJ_FLAG_HIDDEN);
|
||
else lv_obj_add_flag(btn, LV_OBJ_FLAG_HIDDEN);
|
||
bool selected = (current == std::string(m));
|
||
lv_obj_set_style_bg_color(btn, lv_color_hex(selected ? 0x00A6ED : 0xFFFFFF), LV_PART_MAIN);
|
||
lv_obj_set_style_text_color(lv_obj_get_child(btn, 0), lv_color_hex(selected ? 0xFFFFFF : 0x1B1B1B), 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");
|
||
// Fan and swing rows: the entity's own mode names, at most four each, as Home Assistant reports them.
|
||
auto pretty = [](std::string raw) { if (!raw.empty() && raw[0] >= 'a' && raw[0] <= 'z') raw[0] = (char)(raw[0] - 'a' + 'A'); return raw; };
|
||
auto fill = [&](lv_obj_t *title, lv_obj_t *chips[4], const std::string &modes, const std::string ¤t_mode) {
|
||
unsigned shown = 0;
|
||
for (unsigned i = 0; i < 4; ++i) {
|
||
std::string raw = cyd::list_item(modes, i);
|
||
if (raw.empty()) { lv_obj_add_flag(chips[i], LV_OBJ_FLAG_HIDDEN); continue; }
|
||
lv_obj_clear_flag(chips[i], LV_OBJ_FLAG_HIDDEN); ++shown;
|
||
lv_label_set_text(lv_obj_get_child(chips[i], 0), pretty(raw).c_str());
|
||
lv_obj_set_style_bg_color(chips[i], lv_color_hex(raw == current_mode ? 0x00A6ED : 0xFFFFFF), LV_PART_MAIN);
|
||
lv_obj_set_style_text_color(lv_obj_get_child(chips[i], 0), lv_color_hex(raw == current_mode ? 0xFFFFFF : 0x1B1B1B), LV_PART_MAIN);
|
||
}
|
||
if (shown) lv_obj_clear_flag(title, LV_OBJ_FLAG_HIDDEN); else lv_obj_add_flag(title, LV_OBJ_FLAG_HIDDEN);
|
||
};
|
||
lv_obj_t *fan_chips[4] = {id(cfan_0), id(cfan_1), id(cfan_2), id(cfan_3)};
|
||
lv_obj_t *swing_chips[4] = {id(cswing_0), id(cswing_1), id(cswing_2), id(cswing_3)};
|
||
fill(id(cfan_title), fan_chips, id(active_climate_fan_modes), id(active_climate_fan_mode));
|
||
fill(id(cswing_title), swing_chips, id(active_climate_swing_modes), id(active_climate_swing_mode));
|
||
- lvgl.widget.show: climate_mode_overlay
|
||
|
||
# Zet precies een modus. Geen tussenstappen, dus geen ongewenste
|
||
# commando's naar de warmtepomp onderweg.
|
||
# Fan and swing modes from the picker: one call with the entity's own mode name.
|
||
- id: set_climate_fan_mode
|
||
mode: restart
|
||
parameters:
|
||
index: int
|
||
then:
|
||
- lambda: |-
|
||
id(active_climate_fan_mode) = cyd::list_item(id(active_climate_fan_modes), index);
|
||
id(climate_edit_ms) = millis();
|
||
id(climate_edit_pending) = true;
|
||
- lvgl.widget.hide: climate_mode_overlay
|
||
- if:
|
||
condition:
|
||
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && !id(active_climate_fan_mode).empty();'
|
||
then:
|
||
- homeassistant.action:
|
||
action: climate.set_fan_mode
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
fan_mode: !lambda "return id(active_climate_fan_mode);"
|
||
- id: set_climate_swing_mode
|
||
mode: restart
|
||
parameters:
|
||
index: int
|
||
then:
|
||
- lambda: |-
|
||
id(active_climate_swing_mode) = cyd::list_item(id(active_climate_swing_modes), index);
|
||
id(climate_edit_ms) = millis();
|
||
id(climate_edit_pending) = true;
|
||
- lvgl.widget.hide: climate_mode_overlay
|
||
- if:
|
||
condition:
|
||
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && !id(active_climate_swing_mode).empty();'
|
||
then:
|
||
- homeassistant.action:
|
||
action: climate.set_swing_mode
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
swing_mode: !lambda "return id(active_climate_swing_mode);"
|
||
|
||
# Quick on/off in the climate card; climate.turn_on restores the last mode in Home Assistant.
|
||
- id: climate_power_toggle
|
||
mode: restart
|
||
then:
|
||
- lambda: |-
|
||
std::string mode = id(active_climate_mode);
|
||
for (char &c : mode) if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
|
||
id(active_climate_power_on) = mode == "off" || mode.empty() || mode == "unknown";
|
||
id(climate_edit_ms) = millis();
|
||
id(climate_edit_pending) = true;
|
||
if (!id(active_climate_power_on)) { id(active_climate_mode) = "off"; lv_label_set_text(id(climate_mode_label), "Off"); }
|
||
id(climate_power_refresh).execute();
|
||
- if:
|
||
condition:
|
||
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && id(active_climate_power_on);'
|
||
then:
|
||
- homeassistant.action:
|
||
action: climate.turn_on
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
- if:
|
||
condition:
|
||
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && !id(active_climate_power_on);'
|
||
then:
|
||
- homeassistant.action:
|
||
action: climate.turn_off
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
|
||
# The power key lights up while the device runs in any mode.
|
||
- id: climate_power_refresh
|
||
mode: restart
|
||
then:
|
||
- lambda: |-
|
||
std::string mode = id(active_climate_mode);
|
||
for (char &c : mode) if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
|
||
bool on = !(mode == "off" || mode.empty() || mode == "unknown" || mode == "unavailable");
|
||
if (id(climate_edit_pending)) on = id(active_climate_power_on);
|
||
lv_obj_set_style_bg_color(id(climate_power_button), lv_color_hex(on ? 0xD5EEFC : 0xEEEEEE), LV_PART_MAIN);
|
||
lv_obj_set_style_text_color(id(climate_power_icon), lv_color_hex(on ? 0x0B6E99 : 0x8A8A8A), LV_PART_MAIN);
|
||
|
||
- 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;
|
||
id(active_climate_power_on) = mode != "off";
|
||
id(climate_power_refresh).execute();
|
||
lv_label_set_text(id(climate_mode_label), tile_controls::climate_mode_text(mode));
|
||
- lvgl.widget.hide: climate_mode_overlay
|
||
- if:
|
||
condition:
|
||
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true";'
|
||
then:
|
||
- homeassistant.action:
|
||
action: climate.set_hvac_mode
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
hvac_mode: !lambda "return id(active_climate_mode);"
|
||
|
||
# Isolated 40-second preview; every light commit is suppressed in the helper.
|
||
- id: light_controls_preview
|
||
mode: single
|
||
then:
|
||
- script.execute: wake_display
|
||
- lambda: |-
|
||
light_controls::demo = true;
|
||
light_controls::fallback.minimum = 2000;
|
||
light_controls::fallback.maximum = 6500;
|
||
light_controls::fallback.kelvin = 3000;
|
||
light_controls::fallback.hue = 200;
|
||
light_controls::open("diagnostic", true, true, 75);
|
||
lv_label_set_text(id(color_detail_title), "Voorbeeld lamp");
|
||
- lvgl.widget.hide: color_close_button
|
||
- lvgl.widget.hide: color_done_button
|
||
- lvgl.widget.show: color_detail_overlay
|
||
- delay: 40s
|
||
- lvgl.widget.hide: color_detail_overlay
|
||
- lvgl.widget.show: color_close_button
|
||
- lvgl.widget.show: color_done_button
|
||
- lambda: 'light_controls::demo = false; light_controls::fallback = light_controls::State{};'
|
||
|
||
- id: open_color_detail
|
||
mode: restart
|
||
then:
|
||
- lambda: |-
|
||
lv_label_set_text(id(color_detail_title), id(active_title).c_str());
|
||
light_controls::open(id(active_entity), id(active_supports_color), id(active_supports_temperature), id(active_brightness_pct));
|
||
- lvgl.widget.hide: brightness_overlay
|
||
- lvgl.widget.show: color_detail_overlay
|
||
|
||
- id: color_brightness_commit
|
||
parameters:
|
||
value: int
|
||
then:
|
||
- lambda: 'id(active_brightness_pct) = value;'
|
||
- if:
|
||
condition:
|
||
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && id(active_entity).rfind("light.", 0) == 0;'
|
||
then:
|
||
- homeassistant.action:
|
||
action: light.turn_on
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
brightness_pct: !lambda "return value;"
|
||
|
||
- id: set_light_hue
|
||
parameters:
|
||
value: int
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && id(active_supports_color) && id(active_entity).rfind("light.", 0) == 0;'
|
||
then:
|
||
- homeassistant.action:
|
||
action: light.turn_on
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
data_template:
|
||
hs_color: "{{ [hue | int, 100] }}"
|
||
variables:
|
||
hue: !lambda "return light_controls::clamp(value, 0, 360);"
|
||
|
||
- id: set_light_color_temperature
|
||
parameters:
|
||
kelvin: int
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: 'return (!runtime_tiles::enabled || runtime_tiles::fresh()) && std::string("${DIRECT_ACTIONS}") == "true" && id(active_supports_temperature) && id(active_entity).rfind("light.", 0) == 0 && light_controls::active->temperature_ready();'
|
||
then:
|
||
- homeassistant.action:
|
||
action: light.turn_on
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
color_temp_kelvin: !lambda "return light_controls::clamp(kelvin, light_controls::active->minimum, light_controls::active->maximum);"
|
||
|
||
- id: slider_preview
|
||
parameters:
|
||
value: int
|
||
then:
|
||
- lambda: |-
|
||
char buf[20];
|
||
snprintf(buf, sizeof(buf), "%d%s", value, id(active_value_suffix).c_str());
|
||
lv_label_set_text(id(overlay_value), buf);
|
||
int pct = value;
|
||
if (pct < 0) pct = 0;
|
||
if (pct > 100) pct = 100;
|
||
std::string value_text;
|
||
|
||
if (id(active_control_kind) == "cover_position") {
|
||
std::string closed_label = id(active_label_off).c_str();
|
||
std::string open_label = id(active_label_on).c_str();
|
||
|
||
if (pct <= 0) {
|
||
value_text = closed_label;
|
||
} else if (pct >= 100) {
|
||
value_text = open_label;
|
||
} else {
|
||
char buf[40];
|
||
snprintf(buf, sizeof(buf), "%s - %d%%", open_label.c_str(), pct);
|
||
value_text = buf;
|
||
}
|
||
} else {
|
||
if (pct <= 0) {
|
||
value_text = id(active_label_off).c_str();
|
||
} else {
|
||
char buf[20];
|
||
snprintf(buf, sizeof(buf), "%d%s", pct, id(active_value_suffix).c_str());
|
||
value_text = buf;
|
||
}
|
||
}
|
||
|
||
lv_label_set_text(id(overlay_value), value_text.c_str());
|
||
|
||
|
||
- id: slider_commit
|
||
parameters:
|
||
value: int
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return id(active_control_kind) == "light_brightness";
|
||
then:
|
||
- homeassistant.action:
|
||
action: light.turn_on
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
brightness_pct: !lambda "return value;"
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return id(active_control_kind) == "fan_percentage";
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: "return value <= 0;"
|
||
then:
|
||
- homeassistant.action:
|
||
action: fan.turn_off
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
else:
|
||
- homeassistant.action:
|
||
action: fan.turn_on
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
- delay: 150ms
|
||
- homeassistant.action:
|
||
action: fan.set_percentage
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
percentage: !lambda "return value;"
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return id(active_control_kind) == "cover_position";
|
||
then:
|
||
- homeassistant.action:
|
||
action: cover.set_cover_position
|
||
data:
|
||
entity_id: !lambda "return id(active_entity);"
|
||
position: !lambda "return value;"
|
||
|
||
# ----------------------------------------------------------
|
||
# do_tile_action
|
||
#
|
||
# Centralized handler for executing Home Assistant actions
|
||
# triggered by tile taps.
|
||
#
|
||
# Parameters
|
||
# - tile_type: light | fan | switch | scene | script | cover
|
||
# - tap_action: auto | toggle | activate | fan_toggle_preset
|
||
# - service: optional Home Assistant service override
|
||
# - entity_id: target entity
|
||
# - param_key / param_val: optional parameters for custom services
|
||
# - was_on: previous entity state (pass false for longpress actions)
|
||
#
|
||
# Behavior
|
||
#
|
||
# toggle
|
||
# -> toggles the entity
|
||
# light.toggle / fan.toggle / switch.toggle / cover.open_cover|close_cover (based on was_on)
|
||
#
|
||
# activate
|
||
# -> activates entity
|
||
# scene.turn_on / script.turn_on / light.turn_on / fan.turn_on / cover.open_cover
|
||
#
|
||
# auto
|
||
# -> resolves automatically based on tile_type:
|
||
# light/switch/fan/cover -> toggle
|
||
# scene/script -> activate
|
||
#
|
||
# fan_toggle_preset
|
||
# -> if fan was ON: fan.turn_off
|
||
# -> if fan was OFF:
|
||
# fan.turn_on
|
||
# wait 150ms
|
||
# fan.set_preset_mode(param_val)
|
||
#
|
||
# Custom service
|
||
# If "service" is set, it will be called directly.
|
||
# param_key / param_val may be used for parameters
|
||
# like preset_mode, brightness_pct, percentage, etc.
|
||
# ----------------------------------------------------------
|
||
- id: do_tile_action
|
||
mode: queued
|
||
parameters:
|
||
tile_type: string
|
||
tap_action: string
|
||
service: string
|
||
entity_id: string
|
||
param_key: string
|
||
param_val: string
|
||
was_on: bool
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
if (std::string("${DIRECT_ACTIONS}") != "true") return false;
|
||
// Debounce: zelfde entity binnen 600ms genegeerd. Voorkomt dat
|
||
// touch-ruis dezelfde HA-actie meerdere keren afvuurt (zie
|
||
// globals hierboven voor waarom dit nodig is).
|
||
uint32_t now = millis();
|
||
if (entity_id == id(last_action_entity) && (now - id(last_action_ms)) < 600) {
|
||
ESP_LOGD("tile", "do_tile_action: debounced repeat for %s (%ums na vorige)",
|
||
entity_id.c_str(), now - id(last_action_ms));
|
||
return false;
|
||
}
|
||
id(last_action_entity) = entity_id;
|
||
id(last_action_ms) = now;
|
||
return true;
|
||
then:
|
||
- lambda: |-
|
||
ESP_LOGD("tile", "do_tile_action type=%s action=%s service=%s entity=%s key=%s val=%s was_on=%d",
|
||
tile_type.c_str(), tap_action.c_str(), service.c_str(), entity_id.c_str(),
|
||
param_key.c_str(), param_val.c_str(), (int)was_on);
|
||
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return tap_action == "fan_toggle_preset";
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return was_on;
|
||
then:
|
||
- homeassistant.action:
|
||
action: fan.turn_off
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
else:
|
||
- homeassistant.action:
|
||
action: fan.turn_on
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
- delay: 150ms
|
||
- homeassistant.action:
|
||
action: fan.set_preset_mode
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
preset_mode: !lambda 'return param_val;'
|
||
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
std::string resolved = tap_action;
|
||
if (resolved == "auto") {
|
||
if (tile_type == "scene" || tile_type == "script") resolved = "activate";
|
||
else resolved = "toggle";
|
||
}
|
||
return resolved == "toggle";
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return service.size() > 0;
|
||
then:
|
||
- homeassistant.action:
|
||
action: !lambda 'return service;'
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return tile_type == "light";
|
||
then:
|
||
- homeassistant.action:
|
||
action: light.toggle
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return tile_type == "fan";
|
||
then:
|
||
- homeassistant.action:
|
||
action: fan.toggle
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return tile_type == "switch";
|
||
then:
|
||
- homeassistant.action:
|
||
action: switch.toggle
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return tile_type == "cover";
|
||
then:
|
||
- homeassistant.action:
|
||
action: cover.set_cover_position
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
position: !lambda |-
|
||
return was_on ? 0 : 100;
|
||
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
std::string resolved = tap_action;
|
||
if (resolved == "auto") {
|
||
if (tile_type == "scene" || tile_type == "script") resolved = "activate";
|
||
else resolved = "toggle";
|
||
}
|
||
return resolved == "activate";
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return service.size() > 0;
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return param_key == "preset_mode";
|
||
then:
|
||
- homeassistant.action:
|
||
action: !lambda 'return service;'
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
preset_mode: !lambda 'return param_val;'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return param_key == "brightness_pct";
|
||
then:
|
||
- homeassistant.action:
|
||
action: !lambda 'return service;'
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
brightness_pct: !lambda 'return atoi(param_val.c_str());'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return param_key == "percentage";
|
||
then:
|
||
- homeassistant.action:
|
||
action: !lambda 'return service;'
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
percentage: !lambda 'return atoi(param_val.c_str());'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return param_key == "option";
|
||
then:
|
||
- homeassistant.action:
|
||
action: !lambda 'return service;'
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
option: !lambda 'return param_val;'
|
||
else:
|
||
- homeassistant.action:
|
||
action: !lambda 'return service;'
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return tile_type == "scene";
|
||
then:
|
||
- homeassistant.action:
|
||
action: scene.turn_on
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return tile_type == "script";
|
||
then:
|
||
- homeassistant.action:
|
||
action: script.turn_on
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return tile_type == "light";
|
||
then:
|
||
- homeassistant.action:
|
||
action: light.turn_on
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return tile_type == "fan";
|
||
then:
|
||
- homeassistant.action:
|
||
action: fan.turn_on
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return tile_type == "switch";
|
||
then:
|
||
- homeassistant.action:
|
||
action: switch.turn_on
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
else:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
return tile_type == "cover";
|
||
then:
|
||
- homeassistant.action:
|
||
action: cover.open_cover
|
||
data:
|
||
entity_id: !lambda 'return entity_id;'
|
||
|
||
- id: open_value_overlay
|
||
parameters:
|
||
title: string
|
||
entity: string
|
||
tile_type: string
|
||
longpress_mode: string
|
||
was_on: bool
|
||
off_value: int
|
||
brightness_value: float
|
||
percentage_value: float
|
||
position_value: float
|
||
icon_text: string
|
||
slider_bg_color: uint32_t
|
||
slider_fill_color: uint32_t
|
||
slider_icon_color: uint32_t
|
||
label_off: string
|
||
label_on: string
|
||
supported_color_modes: string
|
||
climate_current_temperature: float
|
||
climate_target_temperature: float
|
||
climate_humidity: float
|
||
climate_mode: string
|
||
climate_supported_modes: string
|
||
climate_min_temp: string
|
||
climate_max_temp: string
|
||
climate_step: string
|
||
then:
|
||
- lambda: |-
|
||
// Accentkleur uit het Home Assistant-domein: het stuk voor de punt in
|
||
// het entity_id. script/scene/button vuur je af (ACCENT_ACTION), een
|
||
// light is een light (ACCENT_LIGHT), de rest is apparaat.
|
||
// LET OP: deze helper staat twee keer in dit bestand — hier en in
|
||
// open_value_overlay. Pas ze samen aan.
|
||
auto accent_for = [](const std::string& entity_id) -> uint32_t {
|
||
std::string d = entity_id.substr(0, entity_id.find('.'));
|
||
if (d == "script" || d == "scene" || d == "button" ||
|
||
d == "input_button" || d == "automation") return ${ACCENT_ACTION};
|
||
if (d == "light") return ${ACCENT_LIGHT};
|
||
return ${ACCENT_DEVICE};
|
||
};
|
||
|
||
// De sliderkleur komt van de tegel, dus die kan ook ${ACCENT_AUTO} zijn.
|
||
uint32_t fill_color = (slider_fill_color == ${ACCENT_AUTO})
|
||
? accent_for(entity) : slider_fill_color;
|
||
|
||
std::string mode = longpress_mode;
|
||
auto contains_mode = [](const std::string& modes, const std::string& needle) {
|
||
return modes.find(needle) != std::string::npos;
|
||
};
|
||
if (mode == "auto") {
|
||
if (tile_type == "light") mode = "brightness";
|
||
else if (tile_type == "fan") mode = "percentage";
|
||
else if (tile_type == "cover") mode = "cover_position";
|
||
else if (tile_type == "climate") mode = "climate_temperature";
|
||
else mode = "none";
|
||
}
|
||
|
||
if (mode == "none") return;
|
||
|
||
id(active_entity) = entity;
|
||
id(active_value_suffix) = " %";
|
||
id(active_label_off) = label_off;
|
||
id(active_label_on) = label_on;
|
||
id(active_title) = title;
|
||
id(active_icon_text) = icon_text;
|
||
id(active_supports_color) = false;
|
||
id(active_supports_temperature) = false;
|
||
lv_obj_add_flag(id(overlay_color_button), LV_OBJ_FLAG_HIDDEN);
|
||
|
||
if (mode == "climate_temperature") {
|
||
auto to_x10 = [](float value, int fallback) -> int {
|
||
if (isnan(value)) return fallback;
|
||
return (int) lround(value * 10.0f);
|
||
};
|
||
auto parse_x10 = [](const std::string& value, int fallback) -> int {
|
||
if (value.empty()) return fallback;
|
||
return (int) lround(atof(value.c_str()) * 10.0f);
|
||
};
|
||
id(climate_edit_pending) = false;
|
||
id(active_control_kind) = "climate_temperature";
|
||
id(active_climate_min_x10) = parse_x10(climate_min_temp, 50);
|
||
id(active_climate_max_x10) = parse_x10(climate_max_temp, 300);
|
||
id(active_climate_step_x10) = parse_x10(climate_step, 5);
|
||
if (id(active_climate_step_x10) < 5) id(active_climate_step_x10) = 5;
|
||
if (id(active_climate_max_x10) <= id(active_climate_min_x10)) {
|
||
id(active_climate_min_x10) = 50;
|
||
id(active_climate_max_x10) = 300;
|
||
}
|
||
|
||
id(active_climate_current_x10) = to_x10(climate_current_temperature, 0);
|
||
int fallback_target = isnan(climate_current_temperature) ? 200 : id(active_climate_current_x10);
|
||
id(active_climate_target_x10) = to_x10(climate_target_temperature, fallback_target);
|
||
if (id(active_climate_target_x10) < id(active_climate_min_x10)) id(active_climate_target_x10) = id(active_climate_min_x10);
|
||
if (id(active_climate_target_x10) > id(active_climate_max_x10)) id(active_climate_target_x10) = id(active_climate_max_x10);
|
||
id(active_climate_target_x10) = id(active_climate_min_x10) + (int) lround((id(active_climate_target_x10) - id(active_climate_min_x10)) / (float) id(active_climate_step_x10)) * id(active_climate_step_x10);
|
||
if (id(active_climate_target_x10) < id(active_climate_min_x10)) id(active_climate_target_x10) = id(active_climate_min_x10);
|
||
if (id(active_climate_target_x10) > id(active_climate_max_x10)) id(active_climate_target_x10) = id(active_climate_max_x10);
|
||
id(active_climate_humidity) = isnan(climate_humidity) ? -1 : (int) lround(climate_humidity);
|
||
id(active_climate_mode) = climate_mode.empty() ? std::string("Auto") : climate_mode;
|
||
id(active_climate_supported_modes) = climate_supported_modes;
|
||
|
||
lv_label_set_text(id(climate_detail_title), title.c_str());
|
||
|
||
char current_buf[48];
|
||
if (isnan(climate_current_temperature)) {
|
||
snprintf(current_buf, sizeof(current_buf), "Nu: --");
|
||
} else if (id(active_climate_humidity) >= 0) {
|
||
snprintf(current_buf, sizeof(current_buf), "Nu: %.1f° · %d%%", climate_current_temperature, id(active_climate_humidity));
|
||
} else {
|
||
snprintf(current_buf, sizeof(current_buf), "Nu: %.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), tile_controls::climate_mode_text(id(active_climate_mode)));
|
||
id(climate_power_refresh).execute();
|
||
|
||
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:
|
||
- if:
|
||
condition:
|
||
lambda: 'return id(active_supports_color) || id(active_supports_temperature);'
|
||
then:
|
||
- script.execute: open_color_detail
|
||
else:
|
||
- lvgl.widget.show: brightness_overlay
|
||
|
||
# UI refresh (values + time + styles) + render
|
||
- id: ui_refresh
|
||
mode: single
|
||
then:
|
||
- delay: 100ms
|
||
- lambda: |-
|
||
if (runtime_tiles::enabled) {
|
||
runtime_tiles::render(id(lbl_room));
|
||
auto now = id(homeassistant_time).now();
|
||
if (screen_settings::current.show_clock) lv_obj_clear_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN);
|
||
else lv_obj_add_flag(id(lbl_time), LV_OBJ_FLAG_HIDDEN);
|
||
if (now.is_valid()) runtime_tiles::label(id(lbl_time), now.strftime(screen_settings::current.clock_24h ? "%H:%M" : "%I:%M"));
|
||
return;
|
||
}
|
||
auto set_lbl = [](lv_obj_t* lbl, const std::string& s) {
|
||
if (lbl && s != lv_label_get_text(lbl)) lv_label_set_text(lbl, s.c_str());
|
||
};
|
||
|
||
auto set_value_auto = [&](lv_obj_t* lbl,
|
||
bool on,
|
||
const std::string& tile_type,
|
||
const std::string& value_mode,
|
||
float brightness,
|
||
float percentage,
|
||
const std::string& preset,
|
||
float climate_current_temperature,
|
||
float climate_target_temperature,
|
||
float climate_humidity,
|
||
const std::string& label_on,
|
||
const std::string& label_off) {
|
||
std::string mode = value_mode;
|
||
|
||
if (mode == "auto" || mode.empty()) {
|
||
if (tile_type == "light") {
|
||
mode = "brightness";
|
||
} else if (tile_type == "fan") {
|
||
mode = preset.empty() ? "percentage" : "preset";
|
||
} else if (tile_type == "climate") {
|
||
mode = "climate";
|
||
} else {
|
||
mode = "text";
|
||
}
|
||
}
|
||
|
||
if (mode == "brightness") {
|
||
if (!on) {
|
||
set_lbl(lbl, label_off);
|
||
return;
|
||
}
|
||
if (isnan(brightness)) {
|
||
set_lbl(lbl, "100 %");
|
||
return;
|
||
}
|
||
int pct = (int) lround((brightness / 255.0f) * 100.0f);
|
||
char buf[8];
|
||
snprintf(buf, sizeof(buf), "%d %%", pct);
|
||
set_lbl(lbl, std::string(buf));
|
||
return;
|
||
}
|
||
|
||
if (mode == "percentage") {
|
||
if (!on) {
|
||
set_lbl(lbl, label_off);
|
||
return;
|
||
}
|
||
int pct = isnan(percentage) ? 0 : (int) lround(percentage);
|
||
char buf[8];
|
||
snprintf(buf, sizeof(buf), "%d %%", pct);
|
||
set_lbl(lbl, std::string(buf));
|
||
return;
|
||
}
|
||
|
||
if (mode == "preset") {
|
||
if (!on) {
|
||
set_lbl(lbl, label_off);
|
||
return;
|
||
}
|
||
if (!preset.empty()) {
|
||
set_lbl(lbl, preset);
|
||
} else {
|
||
set_lbl(lbl, label_on);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (mode == "climate") {
|
||
if (!on) {
|
||
set_lbl(lbl, label_off);
|
||
return;
|
||
}
|
||
|
||
float temp = isnan(climate_target_temperature) ? climate_current_temperature : climate_target_temperature;
|
||
if (isnan(temp)) {
|
||
set_lbl(lbl, label_on);
|
||
return;
|
||
}
|
||
|
||
char buf[16];
|
||
snprintf(buf, sizeof(buf), "%.1f °", temp);
|
||
set_lbl(lbl, std::string(buf));
|
||
return;
|
||
}
|
||
|
||
if (mode == "text") {
|
||
if (tile_type == "scene" || tile_type == "script") {
|
||
set_lbl(lbl, label_on);
|
||
} else {
|
||
set_lbl(lbl, on ? label_on : label_off);
|
||
}
|
||
return;
|
||
}
|
||
|
||
set_lbl(lbl, "—");
|
||
};
|
||
|
||
// active_state niet leeg? Dan telt alleen die exacte HA-state als "aan".
|
||
// Zo kan een input_select ("Vol"/"Leeg") ook een tegel laten oplichten.
|
||
auto is_active = [](const std::string& tile_type, const std::string& state,
|
||
const std::string& active_state = std::string()) {
|
||
if (!active_state.empty()) return state == active_state;
|
||
if (tile_type == "climate") {
|
||
return !state.empty() && state != "off" && state != "unavailable" && state != "unknown";
|
||
}
|
||
if (tile_type == "vacuum") return state == "cleaning" || state == "returning";
|
||
if (tile_type == "scene" || tile_type == "script") return true;
|
||
return state == "on";
|
||
};
|
||
|
||
// Cover-aware active state: "closed" = off, anything else (open/opening/closing) = on
|
||
bool t1_on = (std::string("${TILE1_TYPE}") == "cover") ? (id(tile1_cover_state).state != "closed") : is_active("${TILE1_TYPE}", std::string(id(ha_state_tile1).state.c_str()), "${TILE1_ACTIVE_STATE}");
|
||
bool t2_on = (std::string("${TILE2_TYPE}") == "cover") ? (id(tile2_cover_state).state != "closed") : is_active("${TILE2_TYPE}", std::string(id(ha_state_tile2).state.c_str()), "${TILE2_ACTIVE_STATE}");
|
||
bool t3_on = (std::string("${TILE3_TYPE}") == "cover") ? (id(tile3_cover_state).state != "closed") : is_active("${TILE3_TYPE}", std::string(id(ha_state_tile3).state.c_str()), "${TILE3_ACTIVE_STATE}");
|
||
bool t4_on = (std::string("${TILE4_TYPE}") == "cover") ? (id(tile4_cover_state).state != "closed") : is_active("${TILE4_TYPE}", std::string(id(ha_state_tile4).state.c_str()), "${TILE4_ACTIVE_STATE}");
|
||
bool t5_on = (std::string("${TILE5_TYPE}") == "cover") ? (id(tile5_cover_state).state != "closed") : is_active("${TILE5_TYPE}", std::string(id(ha_state_tile5).state.c_str()), "${TILE5_ACTIVE_STATE}");
|
||
bool t6_on = (std::string("${TILE6_TYPE}") == "cover") ? (id(tile6_cover_state).state != "closed") : is_active("${TILE6_TYPE}", std::string(id(ha_state_tile6).state.c_str()), "${TILE6_ACTIVE_STATE}");
|
||
// Tegel 7/8 hebben geen cover_state-sensor, dus geen cover-tak nodig.
|
||
bool t7_on = is_active("${TILE7_TYPE}", std::string(id(ha_state_tile7).state.c_str()), "${TILE7_ACTIVE_STATE}");
|
||
bool t8_on = is_active("${TILE8_TYPE}", std::string(id(ha_state_tile8).state.c_str()), "${TILE8_ACTIVE_STATE}");
|
||
bool t9_on = is_active("${TILE9_TYPE}", std::string(id(ha_state_tile9).state.c_str()), "${TILE9_ACTIVE_STATE}");
|
||
bool t10_on = is_active("${TILE10_TYPE}", std::string(id(ha_state_tile10).state.c_str()), "${TILE10_ACTIVE_STATE}");
|
||
|
||
set_value_auto(id(t1_value), t1_on, "${TILE1_TYPE}", "${TILE1_VALUE_MODE}",
|
||
id(tile1_brightness).state, id(tile1_percentage).state, std::string(id(tile1_preset).state.c_str()),
|
||
id(tile1_climate_current_temperature).state, id(tile1_climate_target_temperature).state, id(tile1_climate_humidity).state,
|
||
"${TILE1_LABEL_ON}", "${TILE1_LABEL_OFF}");
|
||
|
||
set_value_auto(id(t2_value), t2_on, "${TILE2_TYPE}", "${TILE2_VALUE_MODE}",
|
||
id(tile2_brightness).state, id(tile2_percentage).state, std::string(id(tile2_preset).state.c_str()),
|
||
id(tile2_climate_current_temperature).state, id(tile2_climate_target_temperature).state, id(tile2_climate_humidity).state,
|
||
"${TILE2_LABEL_ON}", "${TILE2_LABEL_OFF}");
|
||
|
||
set_value_auto(id(t3_value), t3_on, "${TILE3_TYPE}", "${TILE3_VALUE_MODE}",
|
||
id(tile3_brightness).state, id(tile3_percentage).state, std::string(id(tile3_preset).state.c_str()),
|
||
id(tile3_climate_current_temperature).state, id(tile3_climate_target_temperature).state, id(tile3_climate_humidity).state,
|
||
"${TILE3_LABEL_ON}", "${TILE3_LABEL_OFF}");
|
||
|
||
set_value_auto(id(t4_value), t4_on, "${TILE4_TYPE}", "${TILE4_VALUE_MODE}",
|
||
id(tile4_brightness).state, id(tile4_percentage).state, std::string(id(tile4_preset).state.c_str()),
|
||
id(tile4_climate_current_temperature).state, id(tile4_climate_target_temperature).state, id(tile4_climate_humidity).state,
|
||
"${TILE4_LABEL_ON}", "${TILE4_LABEL_OFF}");
|
||
|
||
set_value_auto(id(t5_value), t5_on, "${TILE5_TYPE}", "${TILE5_VALUE_MODE}",
|
||
id(tile5_brightness).state, id(tile5_percentage).state, std::string(id(tile5_preset).state.c_str()),
|
||
id(tile5_climate_current_temperature).state, id(tile5_climate_target_temperature).state, id(tile5_climate_humidity).state,
|
||
"${TILE5_LABEL_ON}", "${TILE5_LABEL_OFF}");
|
||
|
||
set_value_auto(id(t6_value), t6_on, "${TILE6_TYPE}", "${TILE6_VALUE_MODE}",
|
||
id(tile6_brightness).state, id(tile6_percentage).state, std::string(id(tile6_preset).state.c_str()),
|
||
id(tile6_climate_current_temperature).state, id(tile6_climate_target_temperature).state, id(tile6_climate_humidity).state,
|
||
"${TILE6_LABEL_ON}", "${TILE6_LABEL_OFF}");
|
||
|
||
// Tegel 7: light met alleen brightness. Tegel 8: puur tekst (Vol/Leeg).
|
||
set_value_auto(id(t7_value), t7_on, "${TILE7_TYPE}", "${TILE7_VALUE_MODE}",
|
||
id(tile7_brightness).state, NAN, std::string(),
|
||
NAN, NAN, NAN,
|
||
"${TILE7_LABEL_ON}", "${TILE7_LABEL_OFF}");
|
||
|
||
set_value_auto(id(t8_value), t8_on, "${TILE8_TYPE}", "${TILE8_VALUE_MODE}",
|
||
NAN, NAN, std::string(),
|
||
NAN, NAN, NAN,
|
||
"${TILE8_LABEL_ON}", "${TILE8_LABEL_OFF}");
|
||
|
||
// Tegel 9: kleurenlamp (brightness). Tegel 10: puur tekst (Aan/Uit).
|
||
set_value_auto(id(t9_value), t9_on, "${TILE9_TYPE}", "${TILE9_VALUE_MODE}",
|
||
id(tile9_brightness).state, NAN, std::string(),
|
||
NAN, NAN, NAN,
|
||
"${TILE9_LABEL_ON}", "${TILE9_LABEL_OFF}");
|
||
|
||
set_value_auto(id(t10_value), t10_on, "${TILE10_TYPE}", "${TILE10_VALUE_MODE}",
|
||
NAN, NAN, std::string(),
|
||
NAN, NAN, NAN,
|
||
"${TILE10_LABEL_ON}", "${TILE10_LABEL_OFF}");
|
||
|
||
// Missing state is not an off state. Preserve meaningful vacuum states.
|
||
const char* vacuum_state = id(ha_state_tile6).state.c_str();
|
||
if (std::string("${TILE6_TYPE}") == "vacuum") {
|
||
std::string state(vacuum_state);
|
||
if (state == "cleaning") set_lbl(id(t6_value), "Bezig");
|
||
else if (state == "docked") set_lbl(id(t6_value), "In dock");
|
||
else if (state == "returning") set_lbl(id(t6_value), "Naar dock");
|
||
else if (state == "paused") set_lbl(id(t6_value), "Gepauzeerd");
|
||
else if (state == "error") set_lbl(id(t6_value), "Fout");
|
||
else if (!state.empty()) set_lbl(id(t6_value), state);
|
||
}
|
||
if (!id(ha_state_tile1).has_state() || id(ha_state_tile1).state == "unknown" || id(ha_state_tile1).state == "unavailable")
|
||
set_lbl(id(t1_value), "Niet beschikbaar");
|
||
if (!id(ha_state_tile2).has_state() || id(ha_state_tile2).state == "unknown" || id(ha_state_tile2).state == "unavailable")
|
||
set_lbl(id(t2_value), "Niet beschikbaar");
|
||
if (!id(ha_state_tile3).has_state() || id(ha_state_tile3).state == "unknown" || id(ha_state_tile3).state == "unavailable")
|
||
set_lbl(id(t3_value), "Niet beschikbaar");
|
||
if (!id(ha_state_tile4).has_state() || id(ha_state_tile4).state == "unknown" || id(ha_state_tile4).state == "unavailable")
|
||
set_lbl(id(t4_value), "Niet beschikbaar");
|
||
if (!id(ha_state_tile5).has_state() || id(ha_state_tile5).state == "unknown" || id(ha_state_tile5).state == "unavailable")
|
||
set_lbl(id(t5_value), "Niet beschikbaar");
|
||
if (!id(ha_state_tile6).has_state() || id(ha_state_tile6).state == "unknown" || id(ha_state_tile6).state == "unavailable")
|
||
set_lbl(id(t6_value), "Niet beschikbaar");
|
||
if (!id(ha_state_tile7).has_state() || id(ha_state_tile7).state == "unknown" || id(ha_state_tile7).state == "unavailable")
|
||
set_lbl(id(t7_value), "Niet beschikbaar");
|
||
if (!id(ha_state_tile8).has_state() || id(ha_state_tile8).state == "unknown" || id(ha_state_tile8).state == "unavailable")
|
||
set_lbl(id(t8_value), "Niet beschikbaar");
|
||
if (!id(ha_state_tile9).has_state() || id(ha_state_tile9).state == "unknown" || id(ha_state_tile9).state == "unavailable")
|
||
set_lbl(id(t9_value), "Niet beschikbaar");
|
||
if (!id(ha_state_tile10).has_state() || id(ha_state_tile10).state == "unknown" || id(ha_state_tile10).state == "unavailable")
|
||
set_lbl(id(t10_value), "Niet beschikbaar");
|
||
if (id(active_control_kind) == "climate_temperature" && !lv_obj_has_flag(id(climate_detail_overlay), LV_OBJ_FLAG_HIDDEN)) {
|
||
auto label_for_climate_mode = [](std::string mode) {
|
||
for (char &c : mode) {
|
||
if (c >= 'A' && c <= 'Z') c = (char) (c - 'A' + 'a');
|
||
}
|
||
if (mode == "off") return std::string("Off");
|
||
if (mode == "heat") return std::string("Heat");
|
||
if (mode == "cool") return std::string("Cool");
|
||
if (mode == "heat_cool") return std::string("Heat/Cool");
|
||
if (mode == "auto") return std::string("Auto");
|
||
if (mode == "dry") return std::string("Dry");
|
||
if (mode == "fan_only") return std::string("Fan");
|
||
return mode.empty() ? std::string("Auto") : mode;
|
||
};
|
||
auto sync_open_climate = [&](const std::string& entity,
|
||
float current_temperature,
|
||
float target_temperature,
|
||
float humidity,
|
||
const std::string& climate_mode) {
|
||
if (id(active_entity) != entity) return;
|
||
auto to_x10 = [](float value, int fallback) -> int {
|
||
if (isnan(value)) return fallback;
|
||
return (int) lround(value * 10.0f);
|
||
};
|
||
|
||
id(active_climate_current_x10) = to_x10(current_temperature, id(active_climate_current_x10));
|
||
id(active_climate_humidity) = isnan(humidity) ? -1 : (int) lround(humidity);
|
||
if (!climate_mode.empty() && !id(touch_down) &&
|
||
!(id(climate_edit_pending) && millis() - id(climate_edit_ms) < 3000)) {
|
||
id(active_climate_mode) = climate_mode;
|
||
lv_label_set_text(id(climate_mode_label), label_for_climate_mode(climate_mode).c_str());
|
||
}
|
||
|
||
const bool editing = id(touch_down) ||
|
||
(id(climate_edit_pending) && millis() - id(climate_edit_ms) < 3000);
|
||
if (!editing) id(climate_edit_pending) = false;
|
||
int target_x10 = editing ? id(active_climate_target_x10) :
|
||
to_x10(target_temperature, id(active_climate_target_x10));
|
||
if (target_x10 < id(active_climate_min_x10)) target_x10 = id(active_climate_min_x10);
|
||
if (target_x10 > id(active_climate_max_x10)) target_x10 = id(active_climate_max_x10);
|
||
int step_x10 = id(active_climate_step_x10);
|
||
if (step_x10 < 5) step_x10 = 5;
|
||
target_x10 = id(active_climate_min_x10) + (int) lround((target_x10 - id(active_climate_min_x10)) / (float) step_x10) * step_x10;
|
||
if (target_x10 < id(active_climate_min_x10)) target_x10 = id(active_climate_min_x10);
|
||
if (target_x10 > id(active_climate_max_x10)) target_x10 = id(active_climate_max_x10);
|
||
id(active_climate_target_x10) = target_x10;
|
||
|
||
char current_buf[48];
|
||
if (isnan(current_temperature)) {
|
||
snprintf(current_buf, sizeof(current_buf), "Nu: --");
|
||
} else if (id(active_climate_humidity) >= 0) {
|
||
snprintf(current_buf, sizeof(current_buf), "Nu: %.1f° · %d%%", current_temperature, id(active_climate_humidity));
|
||
} else {
|
||
snprintf(current_buf, sizeof(current_buf), "Nu: %.1f°", current_temperature);
|
||
}
|
||
lv_label_set_text(id(climate_current_label), current_buf);
|
||
|
||
char target_buf[16];
|
||
snprintf(target_buf, sizeof(target_buf), "%.1f°", id(active_climate_target_x10) / 10.0f);
|
||
lv_label_set_text(id(climate_target_label), target_buf);
|
||
lv_arc_set_value(id(climate_arc), id(active_climate_target_x10));
|
||
};
|
||
|
||
if (std::string("${TILE1_TYPE}") == "climate") sync_open_climate("${TILE1_ENTITY}", id(tile1_climate_current_temperature).state, id(tile1_climate_target_temperature).state, id(tile1_climate_humidity).state, std::string(id(tile1_climate_mode).state.c_str()));
|
||
if (std::string("${TILE2_TYPE}") == "climate") sync_open_climate("${TILE2_ENTITY}", id(tile2_climate_current_temperature).state, id(tile2_climate_target_temperature).state, id(tile2_climate_humidity).state, std::string(id(tile2_climate_mode).state.c_str()));
|
||
if (std::string("${TILE3_TYPE}") == "climate") sync_open_climate("${TILE3_ENTITY}", id(tile3_climate_current_temperature).state, id(tile3_climate_target_temperature).state, id(tile3_climate_humidity).state, std::string(id(tile3_climate_mode).state.c_str()));
|
||
if (std::string("${TILE4_TYPE}") == "climate") sync_open_climate("${TILE4_ENTITY}", id(tile4_climate_current_temperature).state, id(tile4_climate_target_temperature).state, id(tile4_climate_humidity).state, std::string(id(tile4_climate_mode).state.c_str()));
|
||
if (std::string("${TILE5_TYPE}") == "climate") sync_open_climate("${TILE5_ENTITY}", id(tile5_climate_current_temperature).state, id(tile5_climate_target_temperature).state, id(tile5_climate_humidity).state, std::string(id(tile5_climate_mode).state.c_str()));
|
||
if (std::string("${TILE6_TYPE}") == "climate") sync_open_climate("${TILE6_ENTITY}", id(tile6_climate_current_temperature).state, id(tile6_climate_target_temperature).state, id(tile6_climate_humidity).state, std::string(id(tile6_climate_mode).state.c_str()));
|
||
}
|
||
|
||
{
|
||
auto now = id(homeassistant_time).now();
|
||
// Vóór de eerste tijd-sync is now() epoch 0 (1970) — dan geen
|
||
// misleidende "01:00" tonen, gewoon leeg laten tot er een
|
||
// echte tijd binnen is (na de eerste Home Assistant-tijdsynchronisatie).
|
||
if (now.is_valid()) {
|
||
char buf[10];
|
||
|
||
if (std::string("${TIME_24H}") == "true") {
|
||
snprintf(buf, sizeof(buf), "%02d:%02d", now.hour, now.minute);
|
||
} else {
|
||
int hour12 = now.hour % 12;
|
||
if (hour12 == 0) hour12 = 12;
|
||
snprintf(buf, sizeof(buf), "%d:%02d %s", hour12, now.minute, now.hour >= 12 ? "PM" : "AM");
|
||
}
|
||
|
||
set_lbl(id(lbl_time), std::string(buf));
|
||
}
|
||
}
|
||
|
||
// Accentkleur uit het Home Assistant-domein: het stuk voor de punt in
|
||
// het entity_id. script/scene/button vuur je af (ACCENT_ACTION), een
|
||
// light is een light (ACCENT_LIGHT), de rest is apparaat.
|
||
// LET OP: deze helper staat twee keer in dit bestand — hier en in
|
||
// open_value_overlay. Pas ze samen aan.
|
||
auto accent_for = [](const std::string& entity_id) -> uint32_t {
|
||
std::string d = entity_id.substr(0, entity_id.find('.'));
|
||
if (d == "script" || d == "scene" || d == "button" ||
|
||
d == "input_button" || d == "automation") return ${ACCENT_ACTION};
|
||
if (d == "light") return ${ACCENT_LIGHT};
|
||
return ${ACCENT_DEVICE};
|
||
};
|
||
|
||
auto apply_tile = [&](bool active,
|
||
const std::string& entity_id,
|
||
lv_obj_t* tile,
|
||
lv_obj_t* circle,
|
||
lv_obj_t* title,
|
||
lv_obj_t* value,
|
||
lv_obj_t* icon_lbl,
|
||
uint32_t circle_active,
|
||
uint32_t circle_disabled,
|
||
uint32_t icon_active,
|
||
uint32_t icon_disabled,
|
||
uint32_t bg_active,
|
||
uint32_t bg_disabled,
|
||
uint32_t title_active,
|
||
uint32_t title_disabled,
|
||
uint32_t value_active,
|
||
uint32_t value_disabled)
|
||
{
|
||
static lv_obj_t* cached_tiles[10] = {};
|
||
static bool cached_active[10] = {};
|
||
for (int i = 0; i < 10; ++i) {
|
||
if (cached_tiles[i] == tile) {
|
||
if (cached_active[i] == active) return;
|
||
cached_active[i] = active;
|
||
break;
|
||
}
|
||
if (cached_tiles[i] == nullptr) {
|
||
cached_tiles[i] = tile;
|
||
cached_active[i] = active;
|
||
break;
|
||
}
|
||
}
|
||
// ${ACCENT_AUTO} = "bepaal zelf"; een echte hexwaarde wint altijd.
|
||
uint32_t accent = accent_for(entity_id);
|
||
if (circle_active == ${ACCENT_AUTO}) circle_active = accent;
|
||
if (icon_disabled == ${ACCENT_AUTO}) icon_disabled = accent;
|
||
|
||
lv_obj_set_style_bg_color(tile, lv_color_hex(active ? bg_active : bg_disabled), 0);
|
||
lv_obj_set_style_bg_color(circle, lv_color_hex(active ? circle_active : circle_disabled), 0);
|
||
lv_obj_set_style_text_color(icon_lbl, lv_color_hex(active ? icon_active : icon_disabled), 0);
|
||
lv_obj_set_style_text_color(title, lv_color_hex(active ? title_active : title_disabled), 0);
|
||
lv_obj_set_style_text_color(value, lv_color_hex(active ? value_active : value_disabled), 0);
|
||
};
|
||
|
||
apply_tile(t1_on, "${TILE1_ENTITY}",
|
||
id(tile1), id(tile1_icon_circle), id(t1_title), id(t1_value), id(tile1_icon_lbl),
|
||
${TILE1_CIRCLE_ACTIVE_COLOR}, ${TILE1_CIRCLE_DISABLED_COLOR},
|
||
${TILE1_ICON_ACTIVE_COLOR}, ${TILE1_ICON_DISABLED_COLOR},
|
||
${TILE1_BG_ACTIVE_COLOR}, ${TILE1_BG_DISABLED_COLOR},
|
||
${TILE1_TITLE_ACTIVE_COLOR}, ${TILE1_TITLE_DISABLED_COLOR},
|
||
${TILE1_VALUE_ACTIVE_COLOR}, ${TILE1_VALUE_DISABLED_COLOR});
|
||
|
||
apply_tile(t2_on, "${TILE2_ENTITY}",
|
||
id(tile2), id(tile2_icon_circle), id(t2_title), id(t2_value), id(tile2_icon_lbl),
|
||
${TILE2_CIRCLE_ACTIVE_COLOR}, ${TILE2_CIRCLE_DISABLED_COLOR},
|
||
${TILE2_ICON_ACTIVE_COLOR}, ${TILE2_ICON_DISABLED_COLOR},
|
||
${TILE2_BG_ACTIVE_COLOR}, ${TILE2_BG_DISABLED_COLOR},
|
||
${TILE2_TITLE_ACTIVE_COLOR}, ${TILE2_TITLE_DISABLED_COLOR},
|
||
${TILE2_VALUE_ACTIVE_COLOR}, ${TILE2_VALUE_DISABLED_COLOR});
|
||
|
||
apply_tile(t3_on, "${TILE3_ENTITY}",
|
||
id(tile3), id(tile3_icon_circle), id(t3_title), id(t3_value), id(tile3_icon_lbl),
|
||
${TILE3_CIRCLE_ACTIVE_COLOR}, ${TILE3_CIRCLE_DISABLED_COLOR},
|
||
${TILE3_ICON_ACTIVE_COLOR}, ${TILE3_ICON_DISABLED_COLOR},
|
||
${TILE3_BG_ACTIVE_COLOR}, ${TILE3_BG_DISABLED_COLOR},
|
||
${TILE3_TITLE_ACTIVE_COLOR}, ${TILE3_TITLE_DISABLED_COLOR},
|
||
${TILE3_VALUE_ACTIVE_COLOR}, ${TILE3_VALUE_DISABLED_COLOR});
|
||
|
||
apply_tile(t4_on, "${TILE4_ENTITY}",
|
||
id(tile4), id(tile4_icon_circle), id(t4_title), id(t4_value), id(tile4_icon_lbl),
|
||
${TILE4_CIRCLE_ACTIVE_COLOR}, ${TILE4_CIRCLE_DISABLED_COLOR},
|
||
${TILE4_ICON_ACTIVE_COLOR}, ${TILE4_ICON_DISABLED_COLOR},
|
||
${TILE4_BG_ACTIVE_COLOR}, ${TILE4_BG_DISABLED_COLOR},
|
||
${TILE4_TITLE_ACTIVE_COLOR}, ${TILE4_TITLE_DISABLED_COLOR},
|
||
${TILE4_VALUE_ACTIVE_COLOR}, ${TILE4_VALUE_DISABLED_COLOR});
|
||
|
||
apply_tile(t5_on, "${TILE5_ENTITY}",
|
||
id(tile5), id(tile5_icon_circle), id(t5_title), id(t5_value), id(tile5_icon_lbl),
|
||
${TILE5_CIRCLE_ACTIVE_COLOR}, ${TILE5_CIRCLE_DISABLED_COLOR},
|
||
${TILE5_ICON_ACTIVE_COLOR}, ${TILE5_ICON_DISABLED_COLOR},
|
||
${TILE5_BG_ACTIVE_COLOR}, ${TILE5_BG_DISABLED_COLOR},
|
||
${TILE5_TITLE_ACTIVE_COLOR}, ${TILE5_TITLE_DISABLED_COLOR},
|
||
${TILE5_VALUE_ACTIVE_COLOR}, ${TILE5_VALUE_DISABLED_COLOR});
|
||
|
||
apply_tile(t6_on, "${TILE6_ENTITY}",
|
||
id(tile6), id(tile6_icon_circle), id(t6_title), id(t6_value), id(tile6_icon_lbl),
|
||
${TILE6_CIRCLE_ACTIVE_COLOR}, ${TILE6_CIRCLE_DISABLED_COLOR},
|
||
${TILE6_ICON_ACTIVE_COLOR}, ${TILE6_ICON_DISABLED_COLOR},
|
||
${TILE6_BG_ACTIVE_COLOR}, ${TILE6_BG_DISABLED_COLOR},
|
||
${TILE6_TITLE_ACTIVE_COLOR}, ${TILE6_TITLE_DISABLED_COLOR},
|
||
${TILE6_VALUE_ACTIVE_COLOR}, ${TILE6_VALUE_DISABLED_COLOR});
|
||
|
||
apply_tile(t7_on, "${TILE7_ENTITY}",
|
||
id(tile7), id(tile7_icon_circle), id(t7_title), id(t7_value), id(tile7_icon_lbl),
|
||
${TILE7_CIRCLE_ACTIVE_COLOR}, ${TILE7_CIRCLE_DISABLED_COLOR},
|
||
${TILE7_ICON_ACTIVE_COLOR}, ${TILE7_ICON_DISABLED_COLOR},
|
||
${TILE7_BG_ACTIVE_COLOR}, ${TILE7_BG_DISABLED_COLOR},
|
||
${TILE7_TITLE_ACTIVE_COLOR}, ${TILE7_TITLE_DISABLED_COLOR},
|
||
${TILE7_VALUE_ACTIVE_COLOR}, ${TILE7_VALUE_DISABLED_COLOR});
|
||
|
||
apply_tile(t8_on, "${TILE8_ENTITY}",
|
||
id(tile8), id(tile8_icon_circle), id(t8_title), id(t8_value), id(tile8_icon_lbl),
|
||
${TILE8_CIRCLE_ACTIVE_COLOR}, ${TILE8_CIRCLE_DISABLED_COLOR},
|
||
${TILE8_ICON_ACTIVE_COLOR}, ${TILE8_ICON_DISABLED_COLOR},
|
||
${TILE8_BG_ACTIVE_COLOR}, ${TILE8_BG_DISABLED_COLOR},
|
||
${TILE8_TITLE_ACTIVE_COLOR}, ${TILE8_TITLE_DISABLED_COLOR},
|
||
${TILE8_VALUE_ACTIVE_COLOR}, ${TILE8_VALUE_DISABLED_COLOR});
|
||
|
||
apply_tile(t9_on, "${TILE9_ENTITY}",
|
||
id(tile9), id(tile9_icon_circle), id(t9_title), id(t9_value), id(tile9_icon_lbl),
|
||
${TILE9_CIRCLE_ACTIVE_COLOR}, ${TILE9_CIRCLE_DISABLED_COLOR},
|
||
${TILE9_ICON_ACTIVE_COLOR}, ${TILE9_ICON_DISABLED_COLOR},
|
||
${TILE9_BG_ACTIVE_COLOR}, ${TILE9_BG_DISABLED_COLOR},
|
||
${TILE9_TITLE_ACTIVE_COLOR}, ${TILE9_TITLE_DISABLED_COLOR},
|
||
${TILE9_VALUE_ACTIVE_COLOR}, ${TILE9_VALUE_DISABLED_COLOR});
|
||
|
||
apply_tile(t10_on, "${TILE10_ENTITY}",
|
||
id(tile10), id(tile10_icon_circle), id(t10_title), id(t10_value), id(tile10_icon_lbl),
|
||
${TILE10_CIRCLE_ACTIVE_COLOR}, ${TILE10_CIRCLE_DISABLED_COLOR},
|
||
${TILE10_ICON_ACTIVE_COLOR}, ${TILE10_ICON_DISABLED_COLOR},
|
||
${TILE10_BG_ACTIVE_COLOR}, ${TILE10_BG_DISABLED_COLOR},
|
||
${TILE10_TITLE_ACTIVE_COLOR}, ${TILE10_TITLE_DISABLED_COLOR},
|
||
${TILE10_VALUE_ACTIVE_COLOR}, ${TILE10_VALUE_DISABLED_COLOR});
|
||
|
||
|
||
# HA blueprint transport; never publishes the JSON command as an HA state.
|
||
text:
|
||
- platform: smart_display
|
||
id: dashboard_inbox
|
||
name: "Tegelinstellingen"
|
||
internal: ${DASHBOARD_INBOX_INTERNAL}
|
||
|
||
button:
|
||
- platform: template
|
||
name: "Touch kalibreren"
|
||
internal: ${DASHBOARD_INBOX_INTERNAL}
|
||
entity_category: config
|
||
on_press:
|
||
- lambda: 'if (runtime_tiles::enabled) screen_calibration::begin();'
|
||
|
||
number:
|
||
- platform: template
|
||
id: setting_brightness
|
||
name: "Helderheid normaal"
|
||
entity_category: config
|
||
min_value: 5
|
||
max_value: 100
|
||
step: 1
|
||
unit_of_measurement: "%"
|
||
update_interval: 2s
|
||
lambda: return screen_settings::current.brightness;
|
||
set_action:
|
||
- lambda: |-
|
||
auto &s = screen_settings::current;
|
||
s.brightness = (int)x;
|
||
s.standby_brightness = std::min(s.standby_brightness,s.brightness);
|
||
s.night_brightness = std::min(s.night_brightness,s.brightness);
|
||
if (runtime_tiles::enabled) runtime_tiles::settings_preference.save(&s);
|
||
runtime_tiles::setting_event("brightness",s.brightness);
|
||
id(apply_screen_settings).execute();
|
||
- platform: template
|
||
id: setting_standby_brightness
|
||
name: "Helderheid standby"
|
||
entity_category: config
|
||
min_value: 0
|
||
max_value: 100
|
||
step: 1
|
||
unit_of_measurement: "%"
|
||
update_interval: 2s
|
||
lambda: return screen_settings::current.standby_brightness;
|
||
set_action:
|
||
- lambda: |-
|
||
auto &s = screen_settings::current;
|
||
s.standby_brightness = (int)x;
|
||
s.standby_brightness = std::min(s.standby_brightness,s.brightness);
|
||
s.night_brightness = std::min(s.night_brightness,s.brightness);
|
||
if (runtime_tiles::enabled) runtime_tiles::settings_preference.save(&s);
|
||
runtime_tiles::setting_event("standby_brightness",s.standby_brightness);
|
||
id(apply_screen_settings).execute();
|
||
- platform: template
|
||
id: setting_night_brightness
|
||
name: "Helderheid nacht"
|
||
entity_category: config
|
||
min_value: 0
|
||
max_value: 100
|
||
step: 1
|
||
unit_of_measurement: "%"
|
||
update_interval: 2s
|
||
lambda: return screen_settings::current.night_brightness;
|
||
set_action:
|
||
- lambda: |-
|
||
auto &s = screen_settings::current;
|
||
s.night_brightness = (int)x;
|
||
s.standby_brightness = std::min(s.standby_brightness,s.brightness);
|
||
s.night_brightness = std::min(s.night_brightness,s.brightness);
|
||
if (runtime_tiles::enabled) runtime_tiles::settings_preference.save(&s);
|
||
runtime_tiles::setting_event("night_brightness",s.night_brightness);
|
||
id(apply_screen_settings).execute();
|
||
- platform: template
|
||
id: setting_standby_seconds
|
||
name: "Standby na"
|
||
entity_category: config
|
||
min_value: 60
|
||
max_value: 86400
|
||
step: 60
|
||
unit_of_measurement: "s"
|
||
update_interval: 2s
|
||
lambda: return screen_settings::current.standby_seconds;
|
||
set_action:
|
||
- lambda: |-
|
||
auto &s = screen_settings::current;
|
||
s.standby_seconds = (int)x;
|
||
s.standby_brightness = std::min(s.standby_brightness,s.brightness);
|
||
s.night_brightness = std::min(s.night_brightness,s.brightness);
|
||
if (runtime_tiles::enabled) runtime_tiles::settings_preference.save(&s);
|
||
runtime_tiles::setting_event("standby_seconds",s.standby_seconds);
|
||
id(apply_screen_settings).execute();
|