Files
homeassistant_espscreen/buttons.yaml
T
Max Gramser 37e653f102 Secretnamen gelijktrekken met de ESPHome-map op Home Assistant
De config staat nu op twee plekken: deze repo en
/config/esphome/smartdisplay.yaml op Home Assistant. Die laatste las de
secrets van HA, deze repo zijn eigen set — met andere waarden voor de
api key en het OTA-wachtwoord. Wie het laatst flashte won, en de ander
kwam er daarna niet meer bij.

Beide configs lezen nu dezelfde namen als /config/esphome/secrets.yaml:

  smartdisplay_api_key -> api_encryption_key
  wifi_ap_password     -> ap_password
  ota_password         -> ongewijzigd van naam, wel HA's waarde

De lokale secrets.yaml (niet in git) is een kopie van die op HA, dus
beide routes leveren exact dezelfde api key en hetzelfde OTA-wachtwoord
op. home-like.yaml en /config/esphome/smartdisplay.yaml zijn nu
byte-identiek.

buttons.yaml krijgt dezelfde omzetting. Die config valideert overigens
al niet meer sinds ESPHome 2026.6 ("Invalid offsets" op de display-
transform); dat stond los van deze wijziging en is hier niet aangeraakt.

Ook opgeruimd: de header verwees nog naar achtergrondplaatjes, die sinds
de gradient niet meer bestaan. Het MDI-font is de enige asset die de
config nog nodig heeft.

Geverifieerd na het gelijktrekken: OTA vanuit de repo werkt, OTA met
HA's credentials werkt, en de ESPHome-integratie in HA bleef verbonden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 13:34:02 +02:00

618 lines
18 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
############################################################
# 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 api_encryption_key
ota:
- platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Smartdisplay Fallback Hotspot"
password: !secret ap_password
captive_portal:
# 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);