Files
esphome/kinderhandy.yaml
2026-01-17 20:10:29 +01:00

382 lines
10 KiB
YAML

# TODO:
# - auto turn off
# - navigation bar at bottom
# - buttons for home assistant actions
substitutions:
device_name: kinderhandy
friendly_name: Kinder Handy
esphome:
name: $device_name
friendly_name: $friendly_name
esp32:
board: esp32dev
framework:
type: arduino
logger:
api:
encryption:
key: !secret api_encryption_key
actions:
- action: rtttl_play
variables:
song_str: string
then:
- rtttl.play:
rtttl: !lambda 'return song_str;'
wifi:
ssid: WLAN
password: !secret wifi_password
# ============================================================
# Hardware
# ============================================================
light:
- platform: monochromatic
output: backlight_pwm
name: Display Backlight
id: display_backlight
restore_mode: ALWAYS_ON
spi:
- id: tft
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
- id: touch
clk_pin: GPIO25
mosi_pin: GPIO32
miso_pin: GPIO39
# Setup a pin to control the backlight
output:
- platform: ledc
pin: GPIO21
id: backlight_pwm
- platform: ledc
pin: GPIO1
id: buzzer_output
display:
- platform: ili9xxx
model: ili9341
spi_id: tft
cs_pin: GPIO15
dc_pin: GPIO2
invert_colors: false
auto_clear_enabled: false
update_interval: never
# Set up the xpt2046 touch platform
touchscreen:
platform: xpt2046
spi_id: touch
cs_pin: GPIO33
interrupt_pin: GPIO36
update_interval: never
threshold: 400
calibration:
x_min: 280
x_max: 3850
y_min: 190
y_max: 3765
transform:
swap_xy: false
mirror_x: true
on_release:
- if:
condition: lvgl.is_paused
then:
- logger.log: "LVGL resuming"
- lvgl.resume:
- lvgl.widget.redraw:
- light.turn_on: display_backlight
# on_touch:
# - lambda: |-
# ESP_LOGI("cal", "x=%d, y=%d, x_raw=%d, y_raw=%0d",
# touch.x,
# touch.y,
# touch.x_raw,
# touch.y_raw
# );
# ============================================================
# Globals
# ============================================================
globals:
- id: timer_seconds
type: int
initial_value: '120'
- id: timer_running
type: bool
initial_value: 'false'
# ============================================================
# Variables
# ============================================================
number:
- platform: template
name: LVGL Screen timeout
optimistic: true
id: display_timeout
unit_of_measurement: "s"
initial_value: 45
restore_value: true
min_value: 10
max_value: 180
step: 5
mode: box
# ============================================================
# Display
# ============================================================
font:
- file: "fonts/RobotoCondensed-Regular.ttf"
id: roboto_icons_42
size: 42
bpp: 4
extras:
- file: "fonts/materialdesignicons-webfont.ttf"
glyphs: [
"\U000F0335", # mdi-lightbulb
"\U000F0954", # mdi-clock
"\U000F0142", # mdi-chevron-right
"\U000F004C", # mdi-bed
"\U000F0761", # mdi-window-shutter-open
"\U000F075E", # mdi-window-shutter
]
lvgl:
on_idle:
timeout: !lambda "return id(timer_running) ? 60 * 5 * 1000: (id(display_timeout).state * 1000);"
then:
- logger.log: "LVGL is idle"
- light.turn_off: display_backlight
- lvgl.pause:
buffer_size: 50%
style_definitions:
- id: header_footer
bg_color: 0x2F8CD8
bg_grad_color: 0x005782
bg_grad_dir: VER
bg_opa: COVER
border_opa: TRANSP
border_width: 0
outline_width: 0
radius: 0
pad_all: 0
pad_row: 0
pad_column: 0
border_color: 0x0077b3
text_color: 0xFFFFFF
width: 100%
height: 60
top_layer:
widgets:
- buttonmatrix:
align: bottom_mid
styles: header_footer
pad_all: 0
outline_width: 0
id: top_layer
items:
styles: header_footer
text_font: roboto_icons_42
rows:
- buttons:
- id: page_light
text: "\U000F0335"
on_press:
then:
- lvgl.page.show: kinderzimmer_page
- id: page_timer
text: "\U000F0954"
on_press:
then:
- lvgl.page.show: timer_page
pages:
- id: timer_page
widgets:
- label:
align: TOP_MID
y: 10
text: "Lukas und Hannas\nZahnputz Timer"
text_font: montserrat_20
text_align: CENTER
- container:
id: arc_container
align: CENTER
y: 15
width: 200
height: 200
on_press:
then:
- if:
condition:
lambda: 'return id(timer_running);'
then:
- globals.set:
id: timer_running
value: 'false'
- logger.log:
format: "Pause"
- lvgl.arc.update:
id: arc_timer
indicator:
arc_color: 0x888888
else:
- globals.set:
id: timer_running
value: 'true'
- logger.log:
format: "Gestartet"
- lvgl.arc.update:
id: arc_timer
value: !lambda return id(timer_seconds);
indicator:
arc_color: 0xFF0000
on_long_press:
then:
- globals.set:
id: timer_seconds
value: '120'
- globals.set:
id: timer_running
value: 'false'
- lvgl.arc.update:
id: arc_timer
value: 0
indicator:
arc_color: 0xFF0000
- lvgl.label.update:
id: timer_label
text: !lambda 'return std::string("02:00");'
- logger.log:
format: "Timer reset (long press)"
widgets:
- arc:
id: arc_timer
align: CENTER
width: 180
height: 180
min_value: 0
max_value: 120
value: 0
arc_width: 8
arc_color: 0x404040
# Indicator arc styling (the progress)
indicator:
arc_width: 20
arc_opa: COVER
arc_color: 0xFF0000 # Start with red
- label:
id: timer_label
align: CENTER
text_font: montserrat_28
text: "02:00"
- id: kinderzimmer_page
widgets:
- label:
align: TOP_MID
y: 10
text: "Kinderzimmer"
text_font: montserrat_20
text_align: CENTER
- buttonmatrix:
align: CENTER
width: 280
height: 200
items:
text_font: roboto_icons_42
rows:
- buttons:
- id: btn_shelf_light
text: "\U000F0335"
- id: btn_cover_open
text: "\U000F0761"
- buttons:
- id: btn_cover_close
text: "\U000F075E"
- id: btn_bed
text: "\U000F004C"
interval:
- interval: 1s
then:
- if:
condition:
lambda: 'return id(timer_running);'
then:
- globals.set:
id: timer_seconds
value: !lambda 'return max(0, (int)id(timer_seconds) - 1);'
- lvgl.arc.update:
id: arc_timer
value: !lambda 'return (int)(id(timer_seconds));'
indicator:
arc_color: !lambda |
float f = (120.0 - (float)id(timer_seconds)) / 120.0;
if (f < 0) f = 0;
if (f > 1) f = 1;
if (f <= 0.5) {
// Red to Yellow (f: 0.0 to 0.5)
float t = f / 0.5;
int r = 255;
int g = (int)(255 * t + 0.5);
int b = 0;
int hex = (r << 16) | (g << 8) | b;
return lv_color_hex(hex);
} else {
// Yellow to Green (f: 0.5 to 1.0)
float t = (f - 0.5) / 0.5;
int r = (int)(255 * (1.0 - t) + 0.5);
int g = 255;
int b = 0;
int hex = (r << 16) | (g << 8) | b;
return lv_color_hex(hex);
}
- lambda: |-
ESP_LOGI("timer", "timer_seconds=%d fraction=%.2f", id(timer_seconds), (float)id(timer_seconds)/120.0);
- lvgl.label.update:
id: timer_label
text: !lambda 'char buf[6]; sprintf(buf, "%02d:%02d", id(timer_seconds)/60, id(timer_seconds)%60); return std::string(buf);'
- if:
condition:
lambda: 'return id(timer_seconds) <= 0;'
then:
- globals.set:
id: timer_running
value: 'false'
- logger.log:
format: "Timer finished"
- lvgl.arc.update:
id: arc_timer
value: 120
indicator:
arc_color: 0x00FF00
# Buzzer
rtttl:
output: buzzer_output
id: my_buzzer_rtttl
gain: 60%
on_finished_playback:
- logger.log: 'Song ended!'
#Entchen:d=4,o=5,b=120:c,d,e,f,2g,2g,a,a,a,a,1g,a,a,a,a,1g,f,f,f,f,2e,2e,g,g,g,g,1c
#MissionImp:d=16,o=6,b=95:32d,32d#,32d,32d#,32d,32d#,32d,32d#,32d,32d,32d#,32e,32f,32f#,32g,g,8p,g,8p,a#,p,c7,p,g,8p,g,8p,f,p,f#,p,g,8p,g,8p,a#,p,c7,p,g,8p,g,8p,f,p,f#,p,a#,g,2d,32p,a#,g,2c#,32p,a#,g,2c,a#5,8c,2p,32p,a#5,g5,2f#,32p,a#5,g5,2f,32p,a#5,g5,2e,d#,8d