substitutions: device_name: ha-family-room-remote friendly_name: "Family Room HA Remote" esphome: name: ${device_name} friendly_name: ${friendly_name} on_boot: priority: -10 then: - light.turn_on: ha_remote_backlight - lvgl.page.show: home - component.update: battery_voltage - lambda: |- id(last_activity_ms) = millis(); esp32: board: esp32-s3-devkitc-1 framework: type: esp-idf logger: level: WARN api: encryption: key: !secret api_encryption_key # Enable over-the-air updates ota: - platform: esphome password: !secret ota_password wifi: ssid: !secret wifi_iot_ssid password: !secret wifi_password power_save_mode: LIGHT fast_connect: true # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "${friendly_name} Fallback" password: !secret fallback_password psram: mode: octal i2c: sda: 8 scl: 9 # CH422G I/O expander (Waveshare uses it for LCD reset/backlight/touch reset) ch422g: - id: ch422g_hub # --- Backlight control (CH422G IO2 is common for Waveshare LCD BL) --- switch: - platform: gpio name: "LCD Backlight Raw" id: lcd_backlight_raw restore_mode: ALWAYS_ON pin: ch422g: ch422g_hub number: 2 mode: output: true # A nicer HA-exposed control (so you can also automate it from HA) light: - platform: binary name: "HA Remote Backlight" output: lcd_backlight_out id: ha_remote_backlight output: - platform: template id: lcd_backlight_out type: binary write_action: - if: condition: lambda: return state; then: - switch.turn_on: lcd_backlight_raw else: - switch.turn_off: lcd_backlight_raw # --- Inactivity tracking (dim/off + wake on touch) --- globals: - id: last_activity_ms type: uint32_t restore_value: no initial_value: '0' interval: - interval: 1s then: - lambda: |- if (id(last_activity_ms) == 0) id(last_activity_ms) = millis(); # Turn off backlight ONCE at 15 seconds idle (no spamming every second) - if: condition: lambda: |- const uint32_t idle_s = (millis() - id(last_activity_ms)) / 1000; return idle_s == 15; then: - light.turn_off: ha_remote_backlight # Deep sleep after 60 seconds idle - if: condition: lambda: |- const uint32_t idle_s = (millis() - id(last_activity_ms)) / 1000; return idle_s >= 60; then: - deep_sleep.enter: deep_sleep_ctrl deep_sleep: id: deep_sleep_ctrl esp32_ext1_wakeup: pins: - 4 mode: ANY_LOW # --- Display --- display: - platform: mipi_rgb model: ESP32-S3-TOUCH-LCD-7-800X480 id: main_display update_interval: never reset_pin: ch422g: ch422g_hub number: 3 mode: output: true # --- Touch --- touchscreen: platform: gt911 id: touch_panel update_interval: 50ms reset_pin: ch422g: ch422g_hub number: 1 mode: output: true on_touch: then: - lambda: |- id(last_activity_ms) = millis(); - light.turn_on: ha_remote_backlight # --- LVGL UI --- sensor: # Waveshare routes battery sensing to EXIO_ADC (GPIO14) through a resistor divider. # Divider on schematic is 470k/100k, so scale ADC by (470+100)/100 = 5.7. - platform: adc id: battery_voltage name: "HA Remote Battery Voltage" pin: GPIO14 attenuation: auto update_interval: 30s device_class: voltage state_class: measurement unit_of_measurement: V accuracy_decimals: 2 filters: - multiply: 5.7 - median: window_size: 7 send_every: 3 send_first_at: 1 - platform: copy source_id: battery_voltage name: "HA Remote Battery Level" id: battery_level device_class: battery state_class: measurement unit_of_measurement: "%" accuracy_decimals: 0 filters: - lambda: |- float pct = (x - 3.20f) * 100.0f / (4.20f - 3.20f); if (pct < 0.0f) return 0.0f; if (pct > 100.0f) return 100.0f; return pct; on_value: then: - lambda: |- if (id(battery_label) == nullptr) return; char txt[24]; snprintf(txt, sizeof(txt), "Battery: %.0f%%", x); lv_label_set_text(id(battery_label), txt); text_sensor: - platform: wifi_info ip_address: name: "HA Remote IP" id: ip_addr lvgl: displays: - main_display touchscreens: - touch_panel disp_bg_color: 0x0A1020 style_definitions: - id: glass_header bg_color: 0x1A2740 bg_opa: 70% border_color: 0xA7C8FF border_width: 1 border_opa: 35% radius: 22 pad_all: 0 shadow_color: 0x061022 shadow_width: 22 shadow_opa: 40% shadow_ofs_x: 0 shadow_ofs_y: 8 - id: glass_chip bg_color: 0xCFE1FF bg_opa: 28% border_color: 0xFFFFFF border_width: 1 border_opa: 45% radius: 18 pad_all: 0 - id: glass_tile_blue bg_color: 0x7BB8FF bg_opa: 30% border_color: 0xE6F1FF border_width: 1 border_opa: 35% radius: 24 shadow_color: 0x091529 shadow_width: 24 shadow_opa: 35% shadow_ofs_x: 0 shadow_ofs_y: 8 text_color: 0xF4F8FF pressed: bg_opa: 46% border_opa: 65% - id: glass_tile_pink bg_color: 0xD99CFF bg_opa: 30% border_color: 0xF7E7FF border_width: 1 border_opa: 35% radius: 24 shadow_color: 0x1A0E2B shadow_width: 24 shadow_opa: 35% shadow_ofs_x: 0 shadow_ofs_y: 8 text_color: 0xFFF8FF pressed: bg_opa: 46% border_opa: 65% - id: glass_tile_amber bg_color: 0xFFD29A bg_opa: 30% border_color: 0xFFF0D9 border_width: 1 border_opa: 35% radius: 24 shadow_color: 0x2A1A08 shadow_width: 24 shadow_opa: 35% shadow_ofs_x: 0 shadow_ofs_y: 8 text_color: 0xFFF9EE pressed: bg_opa: 46% border_opa: 65% - id: glass_tile_green bg_color: 0x9BE2C6 bg_opa: 30% border_color: 0xE4FFF3 border_width: 1 border_opa: 35% radius: 24 shadow_color: 0x0B2018 shadow_width: 24 shadow_opa: 35% shadow_ofs_x: 0 shadow_ofs_y: 8 text_color: 0xF3FFF9 pressed: bg_opa: 46% border_opa: 65% - id: glass_tile_red bg_color: 0xFF9FAF bg_opa: 34% border_color: 0xFFE8EC border_width: 1 border_opa: 38% radius: 24 shadow_color: 0x2A0A12 shadow_width: 24 shadow_opa: 36% shadow_ofs_x: 0 shadow_ofs_y: 8 text_color: 0xFFF5F7 pressed: bg_opa: 50% border_opa: 70% - id: glass_tile_violet bg_color: 0xB8A8FF bg_opa: 30% border_color: 0xF0EAFF border_width: 1 border_opa: 35% radius: 24 shadow_color: 0x1A1230 shadow_width: 24 shadow_opa: 35% shadow_ofs_x: 0 shadow_ofs_y: 8 text_color: 0xF7F3FF pressed: bg_opa: 46% border_opa: 65% theme: label: text_color: 0xEAF2FF button: text_color: 0xF6FAFF pages: - id: home widgets: - obj: x: 14 y: 14 width: 772 height: 84 styles: glass_header widgets: - label: id: status_label x: 22 y: 14 text: "Family Room" - label: x: 22 y: 46 text: "Quick scenes and lighting controls" - obj: x: 594 y: 16 width: 160 height: 52 styles: glass_chip widgets: - label: id: battery_label align: center text: "Battery: --%" - label: x: 22 y: 108 text: "Tap any card. Screen sleeps after 60s idle." - button: x: 24 y: 138 width: 244 height: 136 styles: glass_tile_blue on_click: - homeassistant.service: service: light.toggle data: entity_id: light.living_room widgets: - label: align: center text: "Living Room\nLights" - button: x: 278 y: 138 width: 244 height: 136 styles: glass_tile_pink on_click: - homeassistant.service: service: light.toggle data: entity_id: light.kitchen widgets: - label: align: center text: "Kitchen\nLights" - button: x: 532 y: 138 width: 244 height: 136 styles: glass_tile_red on_click: - homeassistant.service: service: script.turn_off_everything widgets: - label: align: center text: "All Off" - button: x: 24 y: 286 width: 244 height: 136 styles: glass_tile_amber on_click: - homeassistant.service: service: scene.turn_on data: entity_id: scene.movie_time widgets: - label: align: center text: "Movie Time" - button: x: 278 y: 286 width: 244 height: 136 styles: glass_tile_green on_click: - homeassistant.service: service: scene.turn_on data: entity_id: scene.good_night widgets: - label: align: center text: "Good Night" - button: x: 532 y: 286 width: 244 height: 136 styles: glass_tile_violet on_click: - light.toggle: ha_remote_backlight widgets: - label: align: center text: "Backlight\nToggle"