Initial commit: SmartDisplay CYD config met scrollbaar tegelraster
Eerste commit van het project. De config staat op het punt waar het tegelraster scrollbaar is en er acht tegels in staan. Wat er in deze ronde aan de bestaande config veranderd is: - Home screen scrollt. Wallpaper, kamernaam en klok staan op de pagina zelf en blijven staan; alle tegels zitten in een transparante `tile_scroll` container die verticaal scrollt. Ruimte voor 2x5 = 10 tegels, waarvan rij 1-3 zichtbaar is in rust. GRID_ROW*_Y is nu relatief aan die container; SCROLL_Y/SCROLL_H per orientatiepreset. LVGL annuleert klik en long-press zodra een sleep de scroll-limiet passeert, dus tikken en scrollen bijten elkaar niet. - Achtergrond is een twee-stops gradient in plaats van een 320x240 RGB565-wallpaper. Scheelt ~150 KB flash en een image-blit per hertekening, wat het scrollen bruikbaar maakt. Dezelfde gradient wordt hergebruikt als achtergrond van de drie overlays. - Tegel 7: Tafellamp (light.tafellamp_licht), tik = toggle, long-press = helderheidsslider. - Tegel 8: Brievenbus reset (input_select.brievenbus_status). Licht op zodra de status "Vol" is, tik zet hem terug op "Leeg". - TILE*_ACTIVE_STATE toegevoegd: een exacte HA-state die als "actief" telt, voor entiteiten die geen on/off gebruiken. Leeg = oud gedrag. - do_tile_action kent nu param_key "option", voor input_select.select_option. Tegels 7 en 8 abonneren alleen op de HA-entiteiten die ze echt lezen, in plaats van op alle elf per tegel. Na flashen: 148 KB vrije heap, grootste blok 94 KB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
# Gitignore settings for ESPHome
|
||||
# This is an example and may include too much for your use-case.
|
||||
# You can modify this file to suit your needs.
|
||||
/.esphome/
|
||||
/secrets.yaml
|
||||
|
||||
|
||||
# Personal device configuration (not part of the example)
|
||||
testdevice.yaml
|
||||
|
||||
# Handmatige backups van de config
|
||||
*.yaml.bak*
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/esphome-cyd-display.iml" filepath="$PROJECT_DIR$/.idea/esphome-cyd-display.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+19
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,527 @@
|
||||
# Tile Configuration Reference
|
||||
|
||||
This document explains every substitution in `home-like.yaml` and provides copy-paste examples for all supported entity types.
|
||||
|
||||
---
|
||||
|
||||
## How substitutions work
|
||||
|
||||
ESPHome substitutions are compile-time text replacements. There is no runtime logic in the substitution block — every `${TILE1_...}` reference in the YAML is replaced with the value you set before the firmware is compiled and flashed. This means:
|
||||
|
||||
- You cannot use `if/else` in substitutions
|
||||
- Changes require a reflash
|
||||
- The firmware is fully self-contained after flashing
|
||||
|
||||
---
|
||||
|
||||
## The home screen scrolls
|
||||
|
||||
The wallpaper, the room name and the clock sit on the page itself and stay put.
|
||||
All tiles live inside one transparent container (`tile_scroll`) that scrolls
|
||||
vertically, so the grid can be taller than the screen.
|
||||
|
||||
- The grid is **2 columns x 5 rows = 10 tile slots**. Rows 1-3 are visible at
|
||||
rest; dragging up reveals rows 4 and 5.
|
||||
- `GRID_ROW*_Y` are **relative to the scroll container**, not to the page.
|
||||
`SCROLL_Y` / `SCROLL_H` define where the container sits and how tall the
|
||||
viewport is. Both are set per orientation preset.
|
||||
- LVGL derives the scroll length from the tile positions, so there is no
|
||||
content height to maintain by hand.
|
||||
- Tapping and scrolling do not conflict: once a drag passes LVGL's scroll
|
||||
limit (10 px) the short click *and* the long press are cancelled.
|
||||
- `scroll_snap_y: START` makes the container click into place per row. Set it
|
||||
to `NONE` on the `tile_scroll` object for free scrolling.
|
||||
|
||||
## Background
|
||||
|
||||
The background is a two-stop vertical gradient (`BG_TOP_COLOR` ->
|
||||
`BG_BOTTOM_COLOR`), not an image. That saves ~150 KB of flash and removes an
|
||||
image blit from every redraw, which is what makes scrolling usable on this
|
||||
board. The same gradient is reused as the backdrop of the slider, color and
|
||||
thermostat overlays through the `style_bg_grad` style.
|
||||
|
||||
---
|
||||
|
||||
## Global settings
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `DIRECT_ACTIONS` | `"true"` | `true` = device calls HA services directly on tap. `false` = only publishes the action event, you handle it in HA automations. |
|
||||
| `ROOM_NAME` | `"Office"` | Text shown in the top-left header. |
|
||||
| `TIME_24H` | `"true"` | `true` = 24h clock, `false` = 12h with AM/PM. |
|
||||
| `AUTO_DIM` | `"true"` | Enable backlight auto-dimming after inactivity. |
|
||||
| `AUTO_DIM_TIMEOUT` | `"10"` | Seconds of no touch before dimming. |
|
||||
| `MAX_BRIGHTNESS` | `"100"` | Backlight brightness (%) when active. |
|
||||
| `DIM_BRIGHTNESS` | `"40"` | Backlight brightness (%) when dimmed. |
|
||||
| `NIGHT_MODE` | `"true"` | Use a lower brightness level during night hours. |
|
||||
| `NIGHT_START_HOUR` | `"22"` | Hour (0–23) when night mode starts. |
|
||||
| `NIGHT_END_HOUR` | `"7"` | Hour (0–23) when night mode ends. |
|
||||
| `NIGHT_DIM_BRIGHTNESS` | `"18"` | Backlight brightness (%) during night hours. |
|
||||
| `BG_TOP_COLOR` | `"0xAEC4D9"` | Top color of the background gradient. |
|
||||
| `BG_BOTTOM_COLOR` | `"0xD6BB9E"` | Bottom color of the background gradient. |
|
||||
|
||||
---
|
||||
|
||||
## Per-tile substitutions
|
||||
|
||||
Each configured tile has the same set of keys, just with a different number
|
||||
(TILE1 … TILE8). Slots 9 and 10 are free — see [Adding a tile](#adding-a-tile).
|
||||
|
||||
### Basic settings
|
||||
|
||||
| Key | Description |
|
||||
|-----|-------------|
|
||||
| `TILE*_ENTITY` | The Home Assistant entity this tile controls (e.g. `light.living_room`). |
|
||||
| `TILE*_STATE_ENTITY` | Entity used to determine active/inactive visual state. Usually the same as `TILE*_ENTITY`. Set to a different entity if you want the tile to reflect a group or a related sensor. |
|
||||
| `TILE*_TITLE` | Text label shown on the tile. |
|
||||
| `TILE*_ICON` | Material Design Icons Unicode glyph. Find icons at [pictogrammers.com/library/mdi](https://pictogrammers.com/library/mdi/) — click an icon and copy the Unicode value (e.g. `U+F0769` → `"\U000F0769"`). |
|
||||
| `TILE*_TYPE` | Entity type: `light` \| `fan` \| `switch` \| `scene` \| `script` \| `cover` \| `climate`. Controls tap behavior, value display, and slider type in auto mode. |
|
||||
| `TILE*_TAP_ACTION` | What happens on short tap — see [Tap actions](#tap-actions) below. |
|
||||
| `TILE*_LONGPRESS` | What happens on long press — see [Long press modes](#long-press-modes) below. |
|
||||
| `TILE*_VALUE_MODE` | How the value line below the title is rendered — see [Value modes](#value-modes) below. |
|
||||
| `TILE*_LABEL_OFF` | Text shown in the value line when the entity is off / inactive. |
|
||||
| `TILE*_LABEL_ON` | Text shown in the value line when `VALUE_MODE` is `text` and entity is on. |
|
||||
| `TILE*_ACTIVE_STATE` | Optional. Exact Home Assistant state that counts as "active". Leave empty for the default behavior (`on`, or always-on for scenes/scripts). Used by tile 8, where the state is `Vol` / `Leeg` rather than `on` / `off`. |
|
||||
|
||||
### Colors
|
||||
|
||||
All colors are 24-bit hex in the format `"0xRRGGBB"`.
|
||||
|
||||
| Key | Description |
|
||||
|-----|-------------|
|
||||
| `TILE*_CIRCLE_ACTIVE_COLOR` | Icon circle background when active. |
|
||||
| `TILE*_CIRCLE_DISABLED_COLOR` | Icon circle background when inactive. |
|
||||
| `TILE*_ICON_ACTIVE_COLOR` | Icon glyph color when active. |
|
||||
| `TILE*_ICON_DISABLED_COLOR` | Icon glyph color when inactive. |
|
||||
| `TILE*_BG_ACTIVE_COLOR` | Tile background when active. |
|
||||
| `TILE*_BG_DISABLED_COLOR` | Tile background when inactive. |
|
||||
| `TILE*_TITLE_ACTIVE_COLOR` | Title text color when active. |
|
||||
| `TILE*_TITLE_DISABLED_COLOR` | Title text color when inactive. |
|
||||
| `TILE*_VALUE_ACTIVE_COLOR` | Value text color when active. |
|
||||
| `TILE*_VALUE_DISABLED_COLOR` | Value text color when inactive. |
|
||||
|
||||
### Advanced settings (short tap)
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `TILE*_TAP_SERVICE` | `""` | Override the HA service called on tap (e.g. `"light.toggle"`). Leave empty to use the default for the tile type. |
|
||||
| `TILE*_TAP_PARAM_KEY` | `""` | Extra parameter key for the service call. Supported keys: `preset_mode`, `brightness_pct`, `percentage`, `option`. Any other value is ignored and the service is called with `entity_id` only. |
|
||||
| `TILE*_TAP_PARAM_VAL` | `""` | Extra parameter value (e.g. `"Auto"`). |
|
||||
|
||||
### Advanced settings (long press — slider mode)
|
||||
|
||||
Only relevant when `TILE*_LONGPRESS` is set to `"slider"`.
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `TILE*_LONGPRESS_SLIDER` | `"auto"` | Slider type: `auto` \| `brightness` \| `percentage` \| `cover_position` \| `climate_temperature`. `auto` picks the right type based on `TILE*_TYPE`. |
|
||||
| `TILE*_LONGPRESS_OFF_VALUE` | `"0"` | Initial slider position (0–100) shown when the entity is currently off. |
|
||||
|
||||
### Advanced settings (climate tiles)
|
||||
|
||||
Only relevant when `TILE*_TYPE` is set to `"climate"`.
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `TILE*_CLIMATE_MIN_TEMP` | `"5.0"` | Minimum target temperature shown in the thermostat overlay. |
|
||||
| `TILE*_CLIMATE_MAX_TEMP` | `"30.0"` | Maximum target temperature shown in the thermostat overlay. |
|
||||
| `TILE*_CLIMATE_STEP` | `"0.5"` | Step size for the thermostat ring and +/- buttons. Values below `0.5` are rounded up to `0.5`, so the UI always adjusts in whole or half-degree steps. |
|
||||
| `TILE*_CLIMATE_CURRENT_TEMP_ATTRIBUTE` | `"current_temperature"` | Home Assistant attribute used for the current room temperature. |
|
||||
| `TILE*_CLIMATE_TARGET_TEMP_ATTRIBUTE` | `"temperature"` | Home Assistant attribute used for the target temperature. |
|
||||
| `TILE*_CLIMATE_HUMIDITY_ATTRIBUTE` | `"current_humidity"` | Home Assistant attribute used for humidity. Leave the default if the entity does not provide humidity. |
|
||||
|
||||
### Advanced settings (long press — action mode)
|
||||
|
||||
Only relevant when `TILE*_LONGPRESS` is set to `"action"`.
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `TILE*_LONGPRESS_ACTION` | `""` | HA entity to call on long press. Can be completely different from `TILE*_ENTITY`. |
|
||||
| `TILE*_LONGPRESS_ACTION_TYPE` | `""` | Entity type of the long press target: `light` \| `fan` \| `switch` \| `scene` \| `script` \| `cover`. Leave empty to inherit from `TILE*_TYPE`. |
|
||||
| `TILE*_LONGPRESS_ACTION_SERVICE` | `""` | Service to call (default is `activate`/`turn_on`). Set e.g. `"light.toggle"` to toggle instead. |
|
||||
|
||||
---
|
||||
|
||||
## Tap actions
|
||||
|
||||
| Value | Behavior |
|
||||
|-------|----------|
|
||||
| `auto` | Picks the best action for the tile type: `toggle` for light/fan/switch/cover, `activate` for scene/script. Climate tiles do not have a default tap action; use `TILE*_TAP_SERVICE` if you want short tap behavior. |
|
||||
| `toggle` | Toggles the entity (light.toggle / fan.toggle / switch.toggle / cover open↔close). |
|
||||
| `activate` | Turns on / activates the entity (scene.turn_on / script.turn_on / light.turn_on / cover.open_cover). |
|
||||
| `fan_toggle_preset` | If fan is ON: turns it off. If fan is OFF: turns it on and sets a preset mode (configure with `TAP_PARAM_KEY` / `TAP_PARAM_VAL`). |
|
||||
| `custom` | Use `TAP_SERVICE` + `TAP_PARAM_KEY` / `TAP_PARAM_VAL` for a fully custom service call. |
|
||||
|
||||
---
|
||||
|
||||
## Long press modes
|
||||
|
||||
| Value | Behavior |
|
||||
|-------|----------|
|
||||
| `slider` | Opens a value overlay with a slider. Configure with `TILE*_LONGPRESS_SLIDER` and `TILE*_LONGPRESS_OFF_VALUE`. |
|
||||
| `action` | Fires a different HA entity than the short tap. Configure with `TILE*_LONGPRESS_ACTION*`. |
|
||||
| `none` | No direct action — only publishes `tileN_long_press` to HA so you can react in automations. |
|
||||
|
||||
In all modes, the event `tileN_long_press` is always published to `sensor.smartdisplay_action` in Home Assistant.
|
||||
|
||||
For light tiles in `slider` mode, the brightness overlay automatically shows a color-ring button when `DIRECT_ACTIONS` is `"true"` and Home Assistant reports a supported color mode (`hs`, `rgb`, `rgbw`, `rgbww`, `xy`) or color temperature mode (`color_temp`). The color detail view can send `light.turn_on` with `color_name`, `color_temp_kelvin`, and the current brightness percentage. If the light only supports color or only supports color temperature, the Color/Temp tab switch is hidden and only the supported controls are shown. No extra tile substitutions are required.
|
||||
|
||||
For climate tiles in `slider` mode, the thermostat overlay sends `climate.set_temperature` with the configured target temperature. The min/max range, step size, and Home Assistant attribute names are configured through the `TILE*_CLIMATE_*` substitutions. The mode pill cycles through the climate entity's supported `hvac_modes` and sends `climate.set_hvac_mode` when `DIRECT_ACTIONS` is `"true"`.
|
||||
|
||||
---
|
||||
|
||||
## Value modes
|
||||
|
||||
| Value | Behavior |
|
||||
|-------|----------|
|
||||
| `auto` | Picks automatically: `brightness` for lights, `percentage` or `preset` for fans, `climate` for climate entities, `text` for everything else. |
|
||||
| `brightness` | Shows brightness as a percentage (e.g. `80 %`). When off, shows `LABEL_OFF`. |
|
||||
| `percentage` | Shows fan speed as a percentage. When off, shows `LABEL_OFF`. |
|
||||
| `preset` | Shows the fan preset_mode attribute. When off, shows `LABEL_OFF`. |
|
||||
| `climate` | Shows the target temperature, falling back to current temperature if no target is available. When off, shows `LABEL_OFF`. |
|
||||
| `text` | Shows `LABEL_ON` when on, `LABEL_OFF` when off. For scenes/scripts, always shows `LABEL_ON`. |
|
||||
|
||||
---
|
||||
|
||||
## Home Assistant events
|
||||
|
||||
Every tap publishes a short-lived state to `sensor.smartdisplay_action`:
|
||||
|
||||
| Event | Trigger |
|
||||
|-------|---------|
|
||||
| `tile1_press` … `tile8_press` | Short tap on tile 1–8 |
|
||||
| `tile1_long_press` … `tile8_long_press` | Long press on tile 1–8 |
|
||||
|
||||
The state resets to `""` after 500ms. In HA automations, trigger on `state` → `to: "tile1_press"` etc.
|
||||
|
||||
---
|
||||
|
||||
## Complete examples
|
||||
|
||||
### Light — toggle + brightness slider
|
||||
|
||||
```yaml
|
||||
TILE1_ENTITY: "light.living_room_ceiling"
|
||||
TILE1_STATE_ENTITY: "light.living_room_ceiling"
|
||||
TILE1_TITLE: "Ceiling"
|
||||
TILE1_ICON: "\U000F0769" # mdi:ceiling-light
|
||||
TILE1_TYPE: "light"
|
||||
TILE1_TAP_ACTION: "auto" # toggles the light
|
||||
TILE1_LONGPRESS: "slider" # long press opens brightness slider
|
||||
TILE1_VALUE_MODE: "auto" # shows brightness % when on, "Off" when off
|
||||
TILE1_LABEL_OFF: "Off"
|
||||
TILE1_LABEL_ON: "On"
|
||||
# colors
|
||||
TILE1_CIRCLE_ACTIVE_COLOR: "0xFEC600"
|
||||
TILE1_CIRCLE_DISABLED_COLOR: "0x7B7B6F"
|
||||
TILE1_ICON_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE1_ICON_DISABLED_COLOR: "0xFEC600"
|
||||
TILE1_BG_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE1_BG_DISABLED_COLOR: "0x939391"
|
||||
TILE1_TITLE_ACTIVE_COLOR: "0x000000"
|
||||
TILE1_TITLE_DISABLED_COLOR: "0xFFFFFF"
|
||||
TILE1_VALUE_ACTIVE_COLOR: "0x7A7A7C"
|
||||
TILE1_VALUE_DISABLED_COLOR: "0xD9D9D9"
|
||||
# advanced
|
||||
TILE1_TAP_SERVICE: ""
|
||||
TILE1_TAP_PARAM_KEY: ""
|
||||
TILE1_TAP_PARAM_VAL: ""
|
||||
TILE1_LONGPRESS_SLIDER: "auto" # auto = brightness for lights
|
||||
TILE1_LONGPRESS_OFF_VALUE: "0"
|
||||
TILE1_LONGPRESS_ACTION: ""
|
||||
TILE1_LONGPRESS_ACTION_TYPE: ""
|
||||
TILE1_LONGPRESS_ACTION_SERVICE: ""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Switch — simple toggle, no slider
|
||||
|
||||
```yaml
|
||||
TILE2_ENTITY: "switch.office_power_strip"
|
||||
TILE2_STATE_ENTITY: "switch.office_power_strip"
|
||||
TILE2_TITLE: "Power Strip"
|
||||
TILE2_ICON: "\U000F06FF" # mdi:power-socket-eu
|
||||
TILE2_TYPE: "switch"
|
||||
TILE2_TAP_ACTION: "auto" # toggles the switch
|
||||
TILE2_LONGPRESS: "none" # long press only sends event, no slider
|
||||
TILE2_VALUE_MODE: "text" # shows "On" / "Off"
|
||||
TILE2_LABEL_OFF: "Off"
|
||||
TILE2_LABEL_ON: "On"
|
||||
# colors
|
||||
TILE2_CIRCLE_ACTIVE_COLOR: "0xFEC600"
|
||||
TILE2_CIRCLE_DISABLED_COLOR: "0x7B7B6F"
|
||||
TILE2_ICON_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE2_ICON_DISABLED_COLOR: "0xFEC600"
|
||||
TILE2_BG_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE2_BG_DISABLED_COLOR: "0x939391"
|
||||
TILE2_TITLE_ACTIVE_COLOR: "0x000000"
|
||||
TILE2_TITLE_DISABLED_COLOR: "0xFFFFFF"
|
||||
TILE2_VALUE_ACTIVE_COLOR: "0x7A7A7C"
|
||||
TILE2_VALUE_DISABLED_COLOR: "0xD9D9D9"
|
||||
# advanced
|
||||
TILE2_TAP_SERVICE: ""
|
||||
TILE2_TAP_PARAM_KEY: ""
|
||||
TILE2_TAP_PARAM_VAL: ""
|
||||
TILE2_LONGPRESS_SLIDER: "auto"
|
||||
TILE2_LONGPRESS_OFF_VALUE: "0"
|
||||
TILE2_LONGPRESS_ACTION: ""
|
||||
TILE2_LONGPRESS_ACTION_TYPE: ""
|
||||
TILE2_LONGPRESS_ACTION_SERVICE: ""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Cover — open/close toggle + position slider
|
||||
|
||||
```yaml
|
||||
TILE3_ENTITY: "cover.living_room_blinds"
|
||||
TILE3_STATE_ENTITY: "cover.living_room_blinds"
|
||||
TILE3_TITLE: "Blinds"
|
||||
TILE3_ICON: "\U000F111C" # mdi:blinds
|
||||
TILE3_TYPE: "cover"
|
||||
TILE3_TAP_ACTION: "auto" # opens if closed, closes if open
|
||||
TILE3_LONGPRESS: "slider" # long press opens position slider
|
||||
TILE3_VALUE_MODE: "auto" # shows "Open" / "Closed" (text mode for covers)
|
||||
TILE3_LABEL_OFF: "Closed"
|
||||
TILE3_LABEL_ON: "Open"
|
||||
# colors — blue theme for covers
|
||||
TILE3_CIRCLE_ACTIVE_COLOR: "0x00C5EC"
|
||||
TILE3_CIRCLE_DISABLED_COLOR: "0x7B7B6F"
|
||||
TILE3_ICON_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE3_ICON_DISABLED_COLOR: "0x00C5EC"
|
||||
TILE3_BG_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE3_BG_DISABLED_COLOR: "0x939391"
|
||||
TILE3_TITLE_ACTIVE_COLOR: "0x000000"
|
||||
TILE3_TITLE_DISABLED_COLOR: "0xFFFFFF"
|
||||
TILE3_VALUE_ACTIVE_COLOR: "0x7A7A7C"
|
||||
TILE3_VALUE_DISABLED_COLOR: "0xD9D9D9"
|
||||
# advanced
|
||||
TILE3_TAP_SERVICE: ""
|
||||
TILE3_TAP_PARAM_KEY: ""
|
||||
TILE3_TAP_PARAM_VAL: ""
|
||||
TILE3_LONGPRESS_SLIDER: "auto" # auto = cover_position for covers
|
||||
TILE3_LONGPRESS_OFF_VALUE: "0"
|
||||
TILE3_LONGPRESS_ACTION: ""
|
||||
TILE3_LONGPRESS_ACTION_TYPE: ""
|
||||
TILE3_LONGPRESS_ACTION_SERVICE: ""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Fan — toggle preset on tap + speed slider on long press
|
||||
|
||||
```yaml
|
||||
TILE4_ENTITY: "fan.air_purifier"
|
||||
TILE4_STATE_ENTITY: "fan.air_purifier"
|
||||
TILE4_TITLE: "Purifier"
|
||||
TILE4_ICON: "\U000F0D43" # mdi:air-purifier
|
||||
TILE4_TYPE: "fan"
|
||||
TILE4_TAP_ACTION: "fan_toggle_preset" # off→on with preset, on→off
|
||||
TILE4_LONGPRESS: "slider" # long press opens speed slider
|
||||
TILE4_VALUE_MODE: "auto" # shows preset name when on, "Off" when off
|
||||
TILE4_LABEL_OFF: "Off"
|
||||
TILE4_LABEL_ON: "On"
|
||||
# colors — blue theme for fan
|
||||
TILE4_CIRCLE_ACTIVE_COLOR: "0x00C5EC"
|
||||
TILE4_CIRCLE_DISABLED_COLOR: "0x7B7B6F"
|
||||
TILE4_ICON_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE4_ICON_DISABLED_COLOR: "0x00C5EC"
|
||||
TILE4_BG_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE4_BG_DISABLED_COLOR: "0x939391"
|
||||
TILE4_TITLE_ACTIVE_COLOR: "0x000000"
|
||||
TILE4_TITLE_DISABLED_COLOR: "0xFFFFFF"
|
||||
TILE4_VALUE_ACTIVE_COLOR: "0x7A7A7C"
|
||||
TILE4_VALUE_DISABLED_COLOR: "0xD9D9D9"
|
||||
# advanced — fan_toggle_preset requires SERVICE + PARAM
|
||||
TILE4_TAP_SERVICE: "fan.toggle"
|
||||
TILE4_TAP_PARAM_KEY: "preset_mode"
|
||||
TILE4_TAP_PARAM_VAL: "Auto" # preset to activate when turning on
|
||||
TILE4_LONGPRESS_SLIDER: "auto" # auto = percentage for fans
|
||||
TILE4_LONGPRESS_OFF_VALUE: "0"
|
||||
TILE4_LONGPRESS_ACTION: ""
|
||||
TILE4_LONGPRESS_ACTION_TYPE: ""
|
||||
TILE4_LONGPRESS_ACTION_SERVICE: ""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Climate — thermostat ring on long press
|
||||
|
||||
```yaml
|
||||
TILE5_ENTITY: "climate.living_room"
|
||||
TILE5_STATE_ENTITY: "climate.living_room"
|
||||
TILE5_TITLE: "Living"
|
||||
TILE5_ICON: "\U000F0438" # mdi:radiator
|
||||
TILE5_TYPE: "climate"
|
||||
TILE5_TAP_ACTION: "auto" # no default tap action; use TAP_SERVICE if desired
|
||||
TILE5_LONGPRESS: "slider" # long press opens thermostat ring
|
||||
TILE5_VALUE_MODE: "auto" # shows target temperature, or current temperature as fallback
|
||||
TILE5_LABEL_OFF: "Off"
|
||||
TILE5_LABEL_ON: "Heat"
|
||||
# colors
|
||||
TILE5_CIRCLE_ACTIVE_COLOR: "0xFF9F0A"
|
||||
TILE5_CIRCLE_DISABLED_COLOR: "0x7B7B6F"
|
||||
TILE5_ICON_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE5_ICON_DISABLED_COLOR: "0xFF9F0A"
|
||||
TILE5_BG_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE5_BG_DISABLED_COLOR: "0x939391"
|
||||
TILE5_TITLE_ACTIVE_COLOR: "0x000000"
|
||||
TILE5_TITLE_DISABLED_COLOR: "0xFFFFFF"
|
||||
TILE5_VALUE_ACTIVE_COLOR: "0x7A7A7C"
|
||||
TILE5_VALUE_DISABLED_COLOR: "0xD9D9D9"
|
||||
# advanced
|
||||
TILE5_TAP_SERVICE: ""
|
||||
TILE5_TAP_PARAM_KEY: ""
|
||||
TILE5_TAP_PARAM_VAL: ""
|
||||
TILE5_LONGPRESS_SLIDER: "auto" # auto = climate_temperature for climate tiles
|
||||
TILE5_LONGPRESS_OFF_VALUE: "0"
|
||||
TILE5_LONGPRESS_ACTION: ""
|
||||
TILE5_LONGPRESS_ACTION_TYPE: ""
|
||||
TILE5_LONGPRESS_ACTION_SERVICE: ""
|
||||
TILE5_CLIMATE_MIN_TEMP: "5.0"
|
||||
TILE5_CLIMATE_MAX_TEMP: "30.0"
|
||||
TILE5_CLIMATE_STEP: "0.5"
|
||||
TILE5_CLIMATE_CURRENT_TEMP_ATTRIBUTE: "current_temperature"
|
||||
TILE5_CLIMATE_TARGET_TEMP_ATTRIBUTE: "temperature"
|
||||
TILE5_CLIMATE_HUMIDITY_ATTRIBUTE: "current_humidity"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Scene — activate on tap
|
||||
|
||||
```yaml
|
||||
TILE6_ENTITY: "scene.movie_night"
|
||||
TILE6_STATE_ENTITY: "scene.movie_night" # scenes are always "on" so the tile stays active-styled
|
||||
TILE6_TITLE: "Movie"
|
||||
TILE6_ICON: "\U000F0E8B" # mdi:movie-open
|
||||
TILE6_TYPE: "scene"
|
||||
TILE6_TAP_ACTION: "auto" # activates the scene
|
||||
TILE6_LONGPRESS: "none"
|
||||
TILE6_VALUE_MODE: "text" # always shows LABEL_ON for scenes
|
||||
TILE6_LABEL_OFF: "Scene"
|
||||
TILE6_LABEL_ON: "Scene"
|
||||
# colors — green theme
|
||||
TILE6_CIRCLE_ACTIVE_COLOR: "0x4CAF50"
|
||||
TILE6_CIRCLE_DISABLED_COLOR: "0x7B7B6F"
|
||||
TILE6_ICON_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE6_ICON_DISABLED_COLOR: "0x4CAF50"
|
||||
TILE6_BG_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE6_BG_DISABLED_COLOR: "0x939391"
|
||||
TILE6_TITLE_ACTIVE_COLOR: "0x000000"
|
||||
TILE6_TITLE_DISABLED_COLOR: "0xFFFFFF"
|
||||
TILE6_VALUE_ACTIVE_COLOR: "0x7A7A7C"
|
||||
TILE6_VALUE_DISABLED_COLOR: "0xD9D9D9"
|
||||
# advanced
|
||||
TILE6_TAP_SERVICE: ""
|
||||
TILE6_TAP_PARAM_KEY: ""
|
||||
TILE6_TAP_PARAM_VAL: ""
|
||||
TILE6_LONGPRESS_SLIDER: "auto"
|
||||
TILE6_LONGPRESS_OFF_VALUE: "0"
|
||||
TILE6_LONGPRESS_ACTION: ""
|
||||
TILE6_LONGPRESS_ACTION_TYPE: ""
|
||||
TILE6_LONGPRESS_ACTION_SERVICE: ""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Script — run on tap
|
||||
|
||||
```yaml
|
||||
TILE6_ENTITY: "script.good_morning"
|
||||
TILE6_STATE_ENTITY: "script.good_morning"
|
||||
TILE6_TITLE: "Morning"
|
||||
TILE6_ICON: "\U000F0A72" # mdi:weather-sunny
|
||||
TILE6_TYPE: "script"
|
||||
TILE6_TAP_ACTION: "auto" # runs the script
|
||||
TILE6_LONGPRESS: "none"
|
||||
TILE6_VALUE_MODE: "text"
|
||||
TILE6_LABEL_OFF: "Run"
|
||||
TILE6_LABEL_ON: "Run"
|
||||
# colors — orange theme
|
||||
TILE6_CIRCLE_ACTIVE_COLOR: "0xFF6F00"
|
||||
TILE6_CIRCLE_DISABLED_COLOR: "0x7B7B6F"
|
||||
TILE6_ICON_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE6_ICON_DISABLED_COLOR: "0xFF6F00"
|
||||
TILE6_BG_ACTIVE_COLOR: "0xFFFFFF"
|
||||
TILE6_BG_DISABLED_COLOR: "0x939391"
|
||||
TILE6_TITLE_ACTIVE_COLOR: "0x000000"
|
||||
TILE6_TITLE_DISABLED_COLOR: "0xFFFFFF"
|
||||
TILE6_VALUE_ACTIVE_COLOR: "0x7A7A7C"
|
||||
TILE6_VALUE_DISABLED_COLOR: "0xD9D9D9"
|
||||
# advanced
|
||||
TILE6_TAP_SERVICE: ""
|
||||
TILE6_TAP_PARAM_KEY: ""
|
||||
TILE6_TAP_PARAM_VAL: ""
|
||||
TILE6_LONGPRESS_SLIDER: "auto"
|
||||
TILE6_LONGPRESS_OFF_VALUE: "0"
|
||||
TILE6_LONGPRESS_ACTION: ""
|
||||
TILE6_LONGPRESS_ACTION_TYPE: ""
|
||||
TILE6_LONGPRESS_ACTION_SERVICE: ""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Long press — call a different entity
|
||||
|
||||
Short tap toggles a light; long press activates a scene.
|
||||
|
||||
```yaml
|
||||
TILE1_ENTITY: "light.living_room"
|
||||
TILE1_TYPE: "light"
|
||||
TILE1_TAP_ACTION: "auto" # short tap: toggle the light
|
||||
TILE1_LONGPRESS: "action" # long press: fire a different entity
|
||||
# ...
|
||||
TILE1_LONGPRESS_ACTION: "scene.movie_night"
|
||||
TILE1_LONGPRESS_ACTION_TYPE: "scene"
|
||||
TILE1_LONGPRESS_ACTION_SERVICE: "" # empty = default activate for scene
|
||||
```
|
||||
|
||||
Short tap toggles a light; long press also toggles a different light.
|
||||
|
||||
```yaml
|
||||
TILE1_LONGPRESS: "action"
|
||||
TILE1_LONGPRESS_ACTION: "light.bedroom"
|
||||
TILE1_LONGPRESS_ACTION_TYPE: "" # empty = inherits "light" from TILE1_TYPE
|
||||
TILE1_LONGPRESS_ACTION_SERVICE: "light.toggle" # must set toggle explicitly, default is turn_on
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Adding a tile
|
||||
|
||||
Slots 9 and 10 are free: `GRID_ROW5_Y` is already defined in every orientation
|
||||
preset. A tile is not a single block — it is six places in `home-like.yaml`.
|
||||
Copy tile 7 (a plain light) or tile 8 (a service call) and change the number:
|
||||
|
||||
1. **Substitutions** — a `TILE9_*` block. Copy an existing one wholesale.
|
||||
2. **Font glyph** — add `MDI_GLYPH_9` and list it under `materialdesign_icons`.
|
||||
A glyph that is not listed renders as blank space.
|
||||
3. **Sensors** — under `sensor:` / `text_sensor:`. `ha_state_tile9` is the only
|
||||
required one. Add the rest only if the tile actually uses them:
|
||||
`tile9_brightness` (light), `tile9_percentage` + `tile9_preset` (fan),
|
||||
`tile9_cover_position` + `tile9_cover_state` (cover),
|
||||
`tile9_climate_*` (climate), `tile9_supported_color_modes` (color light).
|
||||
Every subscription costs heap, so skip what the tile does not read.
|
||||
4. **Widget** — an `obj` under `tile_scroll`'s `widgets:`, at
|
||||
`x: ${GRID_COL1_X}` / `y: ${GRID_ROW5_Y}`. Pass `NAN` (floats) or `""`
|
||||
(strings) to `open_value_overlay` for any sensor you skipped in step 3.
|
||||
5. **Ack script** — a `tile9_ack` copy under `script:`.
|
||||
6. **Refresh lambda** — three lines in `ui_refresh`: a `t9_on` line next to the
|
||||
others, a `set_value_auto(id(t9_value), …)` call, and an `apply_tile(t9_on, …)`
|
||||
call. Climate tiles also need a `sync_open_climate` line.
|
||||
|
||||
Watch `sensor.*_heap_free` in Home Assistant after flashing. This board has no
|
||||
PSRAM, so free heap is the real ceiling on how many tiles fit.
|
||||
|
||||
---
|
||||
|
||||
## Finding Material Design Icons
|
||||
|
||||
1. Go to [pictogrammers.com/library/mdi](https://pictogrammers.com/library/mdi/)
|
||||
2. Search for an icon name
|
||||
3. Click the icon → copy the Unicode codepoint (e.g. `F0769`)
|
||||
4. Format it as `"\U000F0769"` (always 8 hex digits, padded with leading zeros)
|
||||
|
||||
Make sure the glyph is listed in the `glyphs:` section of the MDI font definition in the YAML (search for `materialdesign_icons`). If you use an icon that is not listed there, it will show as a blank space.
|
||||
+618
@@ -0,0 +1,618 @@
|
||||
############################################################
|
||||
# SmartDisplay (Cheap Yellow Display / CYD) – ESPHome + LVGL lockscreen UI
|
||||
#
|
||||
# Target hardware
|
||||
# - Cheap Yellow Display (CYD)
|
||||
# (ESP32 + ILI9341 320x240 display + XPT2046 touchscreen)
|
||||
#
|
||||
# Purpose
|
||||
# - LVGL renders a simple “lockscreen” UI: time/date + 4 toggle buttons.
|
||||
#
|
||||
# Touch input → Home Assistant
|
||||
# - Every touch publishes an “action” string via a text_sensor:
|
||||
# sensor.smartdisplay_action = "btn1_press" / "btn2_press" / "btn3_press" / "btn4_press"
|
||||
# - The sensor is auto-reset to "" after each press so repeated presses trigger reliably.
|
||||
#
|
||||
# Two supported control modes (choose what fits your setup)
|
||||
# 1) Direct control (DEFAULT, simplest)
|
||||
# - DIRECT_ACTIONS: "true" (default)
|
||||
# - Each touch will:
|
||||
# • publish the action string AND
|
||||
# • directly call a Home Assistant service
|
||||
# (default: light.toggle on BTN*_ENTITY)
|
||||
# - Use this if you simply want to toggle lights without creating automations.
|
||||
#
|
||||
# 2) Automation-only control (custom logic)
|
||||
# - DIRECT_ACTIONS: "false"
|
||||
# - Each touch will ONLY publish the action string.
|
||||
# - You implement the behavior in Home Assistant automations using:
|
||||
# Trigger → State → sensor.smartdisplay_action → To: btn1_press (etc.)
|
||||
#
|
||||
# If you do NOT want to toggle lights
|
||||
# - Either change the homeassistant.action blocks to a different service
|
||||
# (e.g. switch.toggle, script.turn_on, scene.turn_on, ...)
|
||||
# - Or set DIRECT_ACTIONS to "false" and handle everything via HA automations.
|
||||
#
|
||||
# UI updates
|
||||
# - On boot, every minute, and whenever the configured HA entities change
|
||||
# (mirrored into ESPHome to keep LVGL button states in sync).
|
||||
#
|
||||
# What you must adapt before flashing
|
||||
# 1) Secrets: api.encryption.key, ota.password, WiFi credentials (prefer secrets.yaml)
|
||||
# 2) BTN*_ENTITY: Home Assistant entity IDs you want to control/mirror
|
||||
# 3) Touch calibration + transform:
|
||||
# - calibration is panel-specific
|
||||
# - touchscreen.transform MUST match display.transform exactly
|
||||
#
|
||||
# Home Assistant note
|
||||
# - In the ESPHome device settings, enable:
|
||||
# “Allow the device to perform Home Assistant actions”
|
||||
# Otherwise homeassistant.action calls are blocked.
|
||||
############################################################
|
||||
|
||||
# ==========================================================
|
||||
# USER CONFIG (edit this section to match your HA setup)
|
||||
#
|
||||
# - DIRECT_ACTIONS:
|
||||
# "true" -> touch publishes action + calls HA service (light.toggle by default)
|
||||
# "false" -> touch only publishes action (all logic handled in HA)
|
||||
#
|
||||
# - BTN*_ENTITY:
|
||||
# The Home Assistant entity to control and mirror for the button state.
|
||||
# Must exist in Home Assistant.
|
||||
#
|
||||
# - BTN*_LABEL / BTN*_ICON:
|
||||
# Only affects what is shown on the display.
|
||||
# ==========================================================
|
||||
substitutions:
|
||||
# Enable/disable direct Home Assistant service calls on touch.
|
||||
# - "true" -> touching a button also calls light.toggle (plug & play)
|
||||
# - "false" -> touching a button ONLY publishes the action text_sensor
|
||||
DIRECT_ACTIONS: "true"
|
||||
|
||||
# Button 1
|
||||
BTN1_ENTITY: "light.desk_lamp"
|
||||
BTN1_LABEL: "Desk"
|
||||
BTN1_ICON: "\U000F095F"
|
||||
|
||||
# Button 2
|
||||
BTN2_ENTITY: "light.ceiling_light"
|
||||
BTN2_LABEL: "Ceiling"
|
||||
BTN2_ICON: "\U000F0769"
|
||||
|
||||
# Button 3
|
||||
BTN3_ENTITY: "light.globe_lamp"
|
||||
BTN3_LABEL: "Globe"
|
||||
BTN3_ICON: "\U000F08DD"
|
||||
|
||||
# Button 4
|
||||
BTN4_ENTITY: "light.ambient_light"
|
||||
BTN4_LABEL: "Ambient"
|
||||
BTN4_ICON: "\U000F1051"
|
||||
# ==========================================================
|
||||
|
||||
# ============================================================
|
||||
# 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).
|
||||
# ============================================================
|
||||
|
||||
esphome:
|
||||
name: smartdisplay
|
||||
friendly_name: SmartDisplay
|
||||
on_boot:
|
||||
priority: 600
|
||||
then:
|
||||
- script.execute: ui_refresh
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
logger:
|
||||
|
||||
# Credentials are loaded from secrets.yaml — copy secrets.yaml.example to secrets.yaml and fill in your values.
|
||||
api:
|
||||
encryption:
|
||||
key: !secret smartdisplay_api_key
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
ap:
|
||||
ssid: "Smartdisplay Fallback Hotspot"
|
||||
password: !secret wifi_ap_password
|
||||
|
||||
captive_portal:
|
||||
|
||||
# Use Home Assistant time so the display always matches your HA timezone.
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: homeassistant_time
|
||||
on_time:
|
||||
- seconds: 0
|
||||
minutes: /1
|
||||
then:
|
||||
- script.execute: ui_refresh
|
||||
|
||||
font:
|
||||
- file: "gfonts://Roboto@500"
|
||||
id: big_text
|
||||
size: 60
|
||||
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: "gfonts://Roboto@300"
|
||||
id: notice_text
|
||||
size: 16
|
||||
bpp: 4
|
||||
glyphs: ['&','@','!',',','.','?','"','%','(',')','+','-','_',':','°','0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','å','Ä','ä','Ö','ö','Ü','ü','/']
|
||||
|
||||
- file: "gfonts://Roboto@400"
|
||||
id: label
|
||||
size: 13
|
||||
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: 35
|
||||
glyphs: ["\U000F095F", "\U000F0769", "\U000F08DD", "\U000F1051"]
|
||||
|
||||
color:
|
||||
- id: ACTIVE
|
||||
hex: "FEC600"
|
||||
- id: INACTIVE
|
||||
hex: "808080"
|
||||
|
||||
sensor:
|
||||
- platform: wifi_signal
|
||||
name: "Wifi Signal"
|
||||
update_interval: 600s
|
||||
|
||||
- platform: uptime
|
||||
name: "Uptime"
|
||||
id: uptime_s
|
||||
update_interval: 15s
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Node Status"
|
||||
id: system_status
|
||||
|
||||
# Mirror HA entities (used to render the "checked" state of each LVGL button)
|
||||
# These are read-only "views" of HA state for UI sync.
|
||||
- platform: homeassistant
|
||||
id: ha_state_btn1
|
||||
entity_id: ${BTN1_ENTITY}
|
||||
on_state:
|
||||
then:
|
||||
- script.execute: ui_refresh
|
||||
|
||||
- platform: homeassistant
|
||||
id: ha_state_btn2
|
||||
entity_id: ${BTN2_ENTITY}
|
||||
on_state:
|
||||
then:
|
||||
- script.execute: ui_refresh
|
||||
|
||||
- platform: homeassistant
|
||||
id: ha_state_btn3
|
||||
entity_id: ${BTN3_ENTITY}
|
||||
on_state:
|
||||
then:
|
||||
- script.execute: ui_refresh
|
||||
|
||||
- platform: homeassistant
|
||||
id: ha_state_btn4
|
||||
entity_id: ${BTN4_ENTITY}
|
||||
on_state:
|
||||
then:
|
||||
- script.execute: ui_refresh
|
||||
|
||||
text_sensor:
|
||||
# ----------------------------------------------------------
|
||||
# SmartDisplay Action
|
||||
#
|
||||
# This is the main "touch trigger" exported to Home Assistant:
|
||||
# - On touch, we publish one of:
|
||||
# btn1_press / btn2_press / btn3_press / btn4_press
|
||||
# - Then we auto-reset to "" so repeated presses still trigger.
|
||||
#
|
||||
# In HA automations, use a simple state trigger:
|
||||
# - Entity: sensor.smartdisplay_action
|
||||
# - To: btn1_press (etc.)
|
||||
# ----------------------------------------------------------
|
||||
- platform: template
|
||||
id: smartdisplay_action
|
||||
name: "SmartDisplay Action"
|
||||
icon: mdi:gesture-tap-button
|
||||
|
||||
- platform: template
|
||||
name: "Uptime (formatted)"
|
||||
lambda: |-
|
||||
uint32_t dur = (uint32_t) id(uptime_s).state;
|
||||
|
||||
int dys = 0;
|
||||
int hrs = 0;
|
||||
int mnts = 0;
|
||||
|
||||
if (dur >= 86400) { dys = dur / 86400; dur = dur - (dys * 86400); }
|
||||
if (dur >= 3600) { hrs = dur / 3600; dur = dur - (hrs * 3600); }
|
||||
if (dur >= 60) { mnts = dur / 60; dur = dur - (mnts * 60); }
|
||||
|
||||
static char buffer[20];
|
||||
snprintf(buffer, sizeof(buffer), "%dd %02dh %02dm %02ds", dys, hrs, mnts, dur);
|
||||
return {buffer};
|
||||
icon: mdi:clock-start
|
||||
update_interval: 60s
|
||||
|
||||
switch:
|
||||
- platform: restart
|
||||
name: "Restart"
|
||||
|
||||
# Two SPI buses:
|
||||
# - lcd: display
|
||||
# - my_touchscreen: touch controller
|
||||
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
|
||||
|
||||
- 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)
|
||||
# IMPORTANT:
|
||||
# - calibration values are device-specific
|
||||
# - touchscreen.transform must match display.transform exactly
|
||||
touchscreen:
|
||||
platform: xpt2046
|
||||
id: ts_touch
|
||||
spi_id: my_touchscreen
|
||||
cs_pin: 33
|
||||
interrupt_pin: 36
|
||||
update_interval: 50ms
|
||||
threshold: 400
|
||||
|
||||
calibration:
|
||||
x_min: 280
|
||||
x_max: 3860
|
||||
y_min: 340
|
||||
y_max: 3860
|
||||
|
||||
transform:
|
||||
swap_xy: true
|
||||
mirror_x: false
|
||||
mirror_y: false
|
||||
|
||||
# Display configuration (ILI9341)
|
||||
display:
|
||||
- platform: mipi_spi
|
||||
id: my_display
|
||||
spi_id: lcd
|
||||
model: ILI9341
|
||||
cs_pin: 15
|
||||
dc_pin: 2
|
||||
invert_colors: false
|
||||
update_interval: never
|
||||
auto_clear_enabled: false
|
||||
|
||||
transform:
|
||||
swap_xy: true
|
||||
mirror_x: false
|
||||
mirror_y: false
|
||||
|
||||
dimensions:
|
||||
width: 320
|
||||
height: 240
|
||||
|
||||
lvgl:
|
||||
displays:
|
||||
- my_display
|
||||
touchscreens:
|
||||
- touchscreen_id: ts_touch
|
||||
|
||||
theme:
|
||||
obj:
|
||||
bg_color: 0x000000
|
||||
bg_opa: COVER
|
||||
button:
|
||||
radius: 30
|
||||
width: 60
|
||||
height: 60
|
||||
border_width: 0
|
||||
shadow_width: 0
|
||||
bg_color: !lambda return id(INACTIVE);
|
||||
bg_opa: COVER
|
||||
checked:
|
||||
bg_color: 0xFFFFFF
|
||||
bg_opa: COVER
|
||||
|
||||
pages:
|
||||
- id: lockscreen_page
|
||||
bg_color: 0x000000
|
||||
bg_opa: COVER
|
||||
widgets:
|
||||
- label:
|
||||
id: lbl_date
|
||||
x: 0
|
||||
y: 30
|
||||
width: 320
|
||||
text_align: CENTER
|
||||
text_font: notice_text
|
||||
text_color: 0xFFFFFF
|
||||
text: ""
|
||||
|
||||
- label:
|
||||
id: lbl_time
|
||||
x: 0
|
||||
y: 55
|
||||
width: 320
|
||||
text_align: CENTER
|
||||
text_font: big_text
|
||||
text_color: 0xFFFFFF
|
||||
text: ""
|
||||
|
||||
# ---------------------------------------------------
|
||||
# LVGL Button 1
|
||||
# - ALWAYS publishes a HA-friendly action: btn1_press
|
||||
# - Optional direct action: light.toggle for BTN1_ENTITY
|
||||
# ---------------------------------------------------
|
||||
- button:
|
||||
id: btn1
|
||||
x: 25
|
||||
y: 140
|
||||
checkable: true
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text_font: materialdesign_icons
|
||||
text_color: !lambda return id(ACTIVE);
|
||||
text: "${BTN1_ICON}"
|
||||
on_click:
|
||||
# ALWAYS: publish action to Home Assistant (state trigger friendly)
|
||||
- script.execute:
|
||||
id: publish_action
|
||||
action: "btn1_press"
|
||||
|
||||
# OPTIONAL: direct HA service call (plug & play)
|
||||
- if:
|
||||
condition:
|
||||
lambda: |-
|
||||
return std::string("${DIRECT_ACTIONS}") == "true";
|
||||
then:
|
||||
- homeassistant.action:
|
||||
action: light.toggle
|
||||
data:
|
||||
entity_id: ${BTN1_ENTITY}
|
||||
|
||||
- label:
|
||||
x: 25
|
||||
y: 205
|
||||
width: 60
|
||||
text_align: CENTER
|
||||
text_font: label
|
||||
text_color: 0xFFFFFF
|
||||
text: "${BTN1_LABEL}"
|
||||
|
||||
# ---------------------------------------------------
|
||||
# LVGL Button 2
|
||||
# - ALWAYS publishes a HA-friendly action: btn2_press
|
||||
# ---------------------------------------------------
|
||||
- button:
|
||||
id: btn2
|
||||
x: 95
|
||||
y: 140
|
||||
checkable: true
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text_font: materialdesign_icons
|
||||
text_color: !lambda return id(ACTIVE);
|
||||
text: "${BTN2_ICON}"
|
||||
on_click:
|
||||
- script.execute:
|
||||
id: publish_action
|
||||
action: "btn2_press"
|
||||
- if:
|
||||
condition:
|
||||
lambda: |-
|
||||
return std::string("${DIRECT_ACTIONS}") == "true";
|
||||
then:
|
||||
- homeassistant.action:
|
||||
action: light.toggle
|
||||
data:
|
||||
entity_id: ${BTN2_ENTITY}
|
||||
|
||||
- label:
|
||||
x: 95
|
||||
y: 205
|
||||
width: 60
|
||||
text_align: CENTER
|
||||
text_font: label
|
||||
text_color: 0xFFFFFF
|
||||
text: "${BTN2_LABEL}"
|
||||
|
||||
# ---------------------------------------------------
|
||||
# LVGL Button 3
|
||||
# - ALWAYS publishes a HA-friendly action: btn3_press
|
||||
# ---------------------------------------------------
|
||||
- button:
|
||||
id: btn3
|
||||
x: 165
|
||||
y: 140
|
||||
checkable: true
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text_font: materialdesign_icons
|
||||
text_color: !lambda return id(ACTIVE);
|
||||
text: "${BTN3_ICON}"
|
||||
on_click:
|
||||
- script.execute:
|
||||
id: publish_action
|
||||
action: "btn3_press"
|
||||
- if:
|
||||
condition:
|
||||
lambda: |-
|
||||
return std::string("${DIRECT_ACTIONS}") == "true";
|
||||
then:
|
||||
- homeassistant.action:
|
||||
action: light.toggle
|
||||
data:
|
||||
entity_id: ${BTN3_ENTITY}
|
||||
|
||||
- label:
|
||||
x: 165
|
||||
y: 205
|
||||
width: 60
|
||||
text_align: CENTER
|
||||
text_font: label
|
||||
text_color: 0xFFFFFF
|
||||
text: "${BTN3_LABEL}"
|
||||
|
||||
# ---------------------------------------------------
|
||||
# LVGL Button 4
|
||||
# - ALWAYS publishes a HA-friendly action: btn4_press
|
||||
# ---------------------------------------------------
|
||||
- button:
|
||||
id: btn4
|
||||
x: 235
|
||||
y: 140
|
||||
checkable: true
|
||||
widgets:
|
||||
- label:
|
||||
align: CENTER
|
||||
text_font: materialdesign_icons
|
||||
text_color: !lambda return id(ACTIVE);
|
||||
text: "${BTN4_ICON}"
|
||||
on_click:
|
||||
- script.execute:
|
||||
id: publish_action
|
||||
action: "btn4_press"
|
||||
- if:
|
||||
condition:
|
||||
lambda: |-
|
||||
return std::string("${DIRECT_ACTIONS}") == "true";
|
||||
then:
|
||||
- homeassistant.action:
|
||||
action: light.toggle
|
||||
data:
|
||||
entity_id: ${BTN4_ENTITY}
|
||||
|
||||
- label:
|
||||
x: 235
|
||||
y: 205
|
||||
width: 60
|
||||
text_align: CENTER
|
||||
text_font: label
|
||||
text_color: 0xFFFFFF
|
||||
text: "${BTN4_LABEL}"
|
||||
|
||||
script:
|
||||
# ----------------------------------------------------------
|
||||
# publish_action
|
||||
#
|
||||
# Publishes a string to the "SmartDisplay Action" text_sensor and
|
||||
# then clears it again.
|
||||
# ----------------------------------------------------------
|
||||
- id: publish_action
|
||||
mode: queued
|
||||
parameters:
|
||||
action: string
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: smartdisplay_action
|
||||
state: !lambda |-
|
||||
return action;
|
||||
- delay: 200ms
|
||||
- text_sensor.template.publish:
|
||||
id: smartdisplay_action
|
||||
state: ""
|
||||
|
||||
- id: ui_refresh
|
||||
mode: queued
|
||||
then:
|
||||
# Sync LVGL "checked" state with the mirrored HA entity states
|
||||
- lvgl.widget.update:
|
||||
id: btn1
|
||||
state:
|
||||
checked: !lambda return id(ha_state_btn1).state;
|
||||
|
||||
- lvgl.widget.update:
|
||||
id: btn2
|
||||
state:
|
||||
checked: !lambda return id(ha_state_btn2).state;
|
||||
|
||||
- lvgl.widget.update:
|
||||
id: btn3
|
||||
state:
|
||||
checked: !lambda return id(ha_state_btn3).state;
|
||||
|
||||
- lvgl.widget.update:
|
||||
id: btn4
|
||||
state:
|
||||
checked: !lambda return id(ha_state_btn4).state;
|
||||
|
||||
# Render current time (HH:MM)
|
||||
- lvgl.label.update:
|
||||
id: lbl_time
|
||||
text: !lambda |-
|
||||
auto now = id(homeassistant_time).now();
|
||||
static char buf[6];
|
||||
snprintf(buf, sizeof(buf), "%02d:%02d", now.hour, now.minute);
|
||||
return std::string(buf);
|
||||
|
||||
# Render localized date in German (adjust to your language if needed)
|
||||
- lvgl.label.update:
|
||||
id: lbl_date
|
||||
text: !lambda |-
|
||||
static const char* days[] = {"", "Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"};
|
||||
static const char* months[] = {"","Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"};
|
||||
auto now = id(homeassistant_time).now();
|
||||
static char buf[40];
|
||||
snprintf(buf, sizeof(buf), "%s, %d. %s", days[now.day_of_week], now.day_of_month, months[now.month]);
|
||||
return std::string(buf);
|
||||
Binary file not shown.
+5571
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
Reference in New Issue
Block a user