Hoy te enseño a integrar Lilygo T-Display S3 Pro en HA, un dispositivo con el que he dado vida al «Habbit Remote».
Índice
Lilygo T-Display S3 Pro
Con la marca Lilygo tengo una de esas relaciones «amor-odio». Por un lado, me encantan muchos de sus dispositivos (mi favorito, el Lilygo T-Display S3 Long). Por otro, ¿sabes esas marcas que tienen un montón de documentación que nos ayuda a integrar sus dispositivos? Pues Lilygo no es una de ellas.
Pero bueno, «lloriqueos» aparte, hoy te quiero enseñar el Lilygo T-Display S3 Pro. En este caso se trata de un pequeño dispositivo (72*32*18mm), que tiene una pantalla táctil de 2.33 pulgadas (222×480 píxeles) compatible con LVGL, montada sobre una placa ESP32-S3R8 con 8 MB de PSRAM.
Pero lo que, a mi parecer, le hace diferente a otros productos es que incorpora una batería de 470 mAh, un par de botones físicos y (dependiendo del modelo que escojas) una cámara en su parte posterior.

Habbit Remote
Con las características que te acabo de contar y el pequeño tamaño del dispositivo, me parece que reúne todo lo necesario para ser un pequeño mando a distancia que te permita controlar cualquier cosa de Home Assistant.
💡 Si, ya sé que existen mandos a distancia compatibles con Home Assistant, pero todos me parecen caros, enormes y poco personalizables.
La idea surge después de que mi Habbit Desk se haya convertido en uno de los dispositivos más útiles, que uso cada día continuamente mientras estoy en mi escritorio. Por ese motivo lo he bautizado como el «Habbit Remote».
Siguiendo el mismo concepto y adaptando su interfaz, he conseguido crear un dispositivo que es perfecto para tenerlo en cualquier habitación con muchos dispositivos integrados. Además, como puedes apagarlo con un interruptor cuando no lo estás usando, puedes alargar mucho la duración de la batería.
Requisitos previos
Para integrar Lilygo T-Display S3 Pro en HA y montar el Habbit Remote necesitas:
- Haber instalado ESPHome en Home Assistant y tener conocimientos mínimos sobre cómo funciona.
- El Lilygo T-Display S3 Pro. Recuerdan que lo venden con y sin cámara, aunque para este proyecto te valdrá el que no tiene cámara.
- Un cable USB-C para alimentar la placa de DATOS (con un cable de carga no vas a poder instalar el software).
Configuración de ESPHome
Para integrar Lilygo T-Display S3 Pro en HA y flashearlo con el código del Habbit Remote sigue estos pasos:
- Conecta la pantalla a tu ordenador a través del puerto USB-C.
- En Home Assistant, accede al complemento de ESPHome desde el menú lateral y pulsa en “New device”. Pulsa en “Continue” y dale un nombre (por ejemplo, “Habbit Remote”).
- Pulsa en «Next» y a continuación selecciona la opción «ESP32-S3» como tipo de dispositivo. Verás que en el fondo se ha creado un nuevo dispositivo.
- Pulsa en «Skip» y haz clic en el enlace «Edit» del bloque correspondiente al dispositivo que acabas de crear. Copia este código y consérvalo, ya que utilizaremos algún fragmento.
- Reemplázalo por este código, haciendo las siguientes adaptaciones:
- Ajusta el ‘name’ y ‘friendly-name’ como el nombre que le quieras dar a tu asistente.
- Añade la información que te había generado el complemento en el apartado ‘api’ (‘encryption key’). Haz lo mismo para el campo ‘password’ bajo el apartado ‘ota’, y para los campos ‘ssid’ y ‘password’ bajo el apartado ‘wifi’ y ‘ap’.
- De momento es suficiente, ya que prefiero que empieces a personalizarlo tras explicarte cómo funciona cada apartado.
substitutions:
# Device customization
# Personalización del dispositivo
name: habbit_remote
friendly_name: Habbit Remote
logo: "https://aguacatec.es/wp-content/uploads/2025/11/habbit_aguacatec.png"
background_color: 0x000000
text_color: 0xFFFFFF
text_color_secondary: 0x626262
iddle_time: 20s
greeting1: "Habbit Remote"
greeting2: "by aguacatec.es"
# Entities
# Entidades
weather_temperature: sensor.openweathermap_temperature
home_temperature: sensor.home_temperatura
home_humidity: sensor.home_humedad
climate: climate.salon
cover: cover.salon
light_ceiling: switch.zbminil2
light_ambient: light.tira_led_sofa
vacuum: vacuum.roborock_qr_598
tv: media_player.smart_tv
# Other settings
# Otros ajustes
allowed_characters: " ¿?¡!#%'()+,-./:µ³°0123456789ABCDEFGHIJKLMNOPQRSTUVWYZabcdefghijklmnopqrstuvwxyzáéíóú"
################################################################################################################
################################################################################################################
esphome:
name: ${name}
friendly_name: ${friendly_name}
platformio_options:
build_flags:
- '-DBOARD_HAS_PSRAM'
- '-DUSING_DISPLAY_PRO_V1'
- '-DARDUINO_ESP32S3_DEV'
- '-DARDUINO_RUNNING_CORE=1'
- '-DARDUINO_EVENT_RUNNING_CORE=1'
board_build.flash_mode: qio
board_build.f_flash: '80000000L'
board_build.arduino.memory_type: 'qio_opi'
board_upload.maximum_size: '16777216'
board_upload.maximum_ram_size: '327680'
board_upload.speed: '921600'
on_boot:
priority: -200
then:
- delay: 3s
- component.update: battery
esp32:
board: esp32s3box
framework:
type: arduino
flash_size: 16MB
psram:
mode: octal
speed: 80MHz
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "Df4VasddsaoqfLSaq7SOxmSQ234rfeq2"
ota:
- platform: esphome
password: "ewf23F004acb632RFDWQFE23"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Remote Fallback Hotspot"
password: "F32W3c6cd34"
captive_portal:
binary_sensor:
- platform: gpio
pin:
number: 12
inverted: true
name: "Button UP"
id: button1
icon: mdi:arrow-up-bold
on_press:
then:
- if:
condition: lvgl.is_paused
then:
- light.turn_on: backlight
- lvgl.resume:
- lvgl.page.show: page_home
else:
- if:
condition:
lvgl.page.is_showing: page_cover
then:
- homeassistant.action:
action: cover.set_cover_position
data:
entity_id: ${cover}
position: !lambda |-
int current = (int) id(cover_position_value).state;
return min(100, current + 10);
else:
- if:
condition:
lvgl.page.is_showing: page_devices
then:
- lvgl.page.show: page_devices2
else:
- lvgl.page.show: page_devices
- platform: gpio
pin:
number: 16
inverted: true
name: "Button DOWN"
id: button2
icon: mdi:arrow-down-bold
on_press:
then:
- if:
condition: lvgl.is_paused
then:
- light.turn_on: backlight
- lvgl.resume:
- lvgl.page.show: page_home
else:
- if:
condition:
lvgl.page.is_showing: page_cover
then:
- homeassistant.action:
action: cover.set_cover_position
data:
entity_id: ${cover}
position: !lambda |-
int current = (int) id(cover_position_value).state;
return max(0, current - 10);
else:
- if:
condition:
lvgl.page.is_showing: page_devices
then:
- lvgl.page.show: page_devices2
else:
- lvgl.page.show: page_devices
- platform: gpio
pin:
number: 21
inverted: true
name: "Screen Button"
id: button_screen
icon: mdi:circle-outline
filters:
- delayed_off: 1s
on_press:
then:
- if:
condition: lvgl.is_paused
then:
- light.turn_on: backlight
- lvgl.resume:
- lvgl.page.show: page_home
- platform: gpio
pin:
number: 0
inverted: true
internal: True
name: "Boot Button"
id: button_b
- platform: sy6970
sy6970_id: pmu
vbus_connected:
name: "USB Connected"
id: usb_connected
internal: True
charging:
name: "Charging"
id: is_charging
charge_done:
name: "Charge Done"
internal: True
font:
- file: "gfonts://Kanit"
id: font_clock
size: 35
glyphs: ${allowed_characters}
- file: "gfonts://Kanit"
id: font_date
size: 20
glyphs: ${allowed_characters}
- file: "gfonts://Kanit"
id: font_greeting
size: 18
glyphs: ${allowed_characters}
- file: "gfonts://Material+Symbols+Outlined"
id: font_status_icons
size: 25
glyphs: [
"\U0000e430", # sun
"\U0000ebe2", # battery
" ",
]
- file: "gfonts://Material+Symbols+Outlined"
id: font_home_icons
size: 30
glyphs: [
"\U0000e430", # sun
"\U0000e1ff", # thermometer
"\U0000f87e", # water-percent
"\U0000ebe2", # battery
"\U0000e286", # blinds-shade
"\U0000ec1f", # blinds-shade-close
" ",
]
- file: "gfonts://Kanit"
id: font_home_info
size: 25
glyphs: ${allowed_characters}
- file: "gfonts://Material+Symbols+Outlined"
id: font_device_icons
size: 100
glyphs: [
"\U0000f02a", # ceiling-light
"\U0000f168", # fan
"\U0000e286", # blinds-shade
"\U0000ec1f", # blinds-shade-close
"\U0000ef55", # fire
"\U0000efc5", # vacuum
"\U0000e333", # tv
"\U0000ea28", # videogames
"\U0000e1c4", # play
"\U0000e1a2", # pause
"\U0000eaaa", # base
"\U0000eee1", # locate
"\U0000f418", # power
"\U0000ef71", # stop
"\U0000ebfe", # spotlight
" ",
]
- file: "gfonts://Kanit"
id: font_slider_value
size: 60
glyphs: ${allowed_characters}
i2c:
- id: bus_a
sda: 5
scl: 6
image:
- file: ${logo}
id: logo
resize: 180x180
type: RGB565
transparency: alpha_channel
- file: mdi:robot-vacuum
id: icon_vacuum
resize: 100x100
type: BINARY
transparency: chroma_key
- file: mdi:led-strip-variant
id: icon_ledstrip
resize: 100x100
type: BINARY
transparency: chroma_key
interval:
- interval: 60s
then:
- lvgl.label.update:
id: lbl_date
text: !lambda 'return id(esptime).now().strftime("%d/%m/%y");'
- lvgl.label.update:
id: lbl_clock
text: !lambda 'return id(esptime).now().strftime("%H:%M");'
light:
- platform: monochromatic
output: gpio_48_backlight_pwm
name: "Display"
icon: mdi:cellphone
id: backlight
restore_mode: ALWAYS_ON
on_turn_on:
- light.turn_on:
id: backlight
brightness: 100%
output:
- platform: ledc
pin: 48
id: gpio_48_backlight_pwm
channel: 1
frequency: 20000
sensor:
- platform: sy6970
sy6970_id: pmu
battery_voltage:
name: "Battery Voltage"
id: battery_voltage
internal: True
charge_current:
name: "Battery Current"
internal: True
vbus_voltage:
name: "VBUS Voltage"
internal: True
- platform: template
name: "Battery"
id: battery
unit_of_measurement: "%"
device_class: battery
accuracy_decimals: 0
lambda: |-
float v = id(battery_voltage).state;
if (v >= 4.1) return 100.0;
if (v <= 3.3) return 0.0;
return (v - 3.3) / (4.1 - 3.3) * 100.0;
update_interval: 60s
on_value:
then:
- lvgl.label.update:
id: lbl_battery_percentage
text:
format: "%.0f%%"
args:
- id(battery).state
# Home Assistant sensors
# Sensores de Home Assistant
- platform: homeassistant
id: home_icon_weather_temperature
entity_id: ${weather_temperature}
internal: true
on_value:
then:
- lvgl.label.update:
id: lbl_weather_temperature
text:
format: "%.0f °C"
args:
- id(home_icon_weather_temperature).state
- platform: homeassistant
id: home_icon_home_temperature
entity_id: ${home_temperature}
internal: true
on_value:
then:
- lvgl.label.update:
id: lbl_home_temperature
text:
format: "%.0f °C"
args:
- id(home_icon_home_temperature).state
- platform: homeassistant
id: home_icon_home_humidity
entity_id: ${home_humidity}
internal: true
on_value:
then:
- lvgl.label.update:
id: lbl_home_humidity
text:
format: "%.0f%%"
args:
- id(home_icon_home_humidity).state
- platform: homeassistant
id: cover_position_value
entity_id: ${cover}
attribute: current_position
internal: true
filters:
- lambda: |-
if (isnan(x)) { return 0; }
else { return x; }
on_value:
then:
- lvgl.slider.update:
id: slider_cover
value: !lambda 'return (int)x;'
- lambda: |-
int position = (int) id(cover_position_value).state;
char buf[8];
snprintf(buf, sizeof(buf), "%d%%", position);
// Actualizar texto del label LVGL
lv_label_set_text(id(slider_cover_value), buf);
spi:
mosi_pin: 17
clk_pin: 18
sy6970:
id: pmu
address: 0x6A
i2c_id: bus_a
enable_adc: true
charge_enabled: true
input_current_limit: 1500 # mA
charge_voltage: 4208 # mV
charge_current: 512 # mA
update_interval: 30s
text_sensor:
- platform: sy6970
sy6970_id: pmu
charge_status:
name: "Charge Status"
internal: True
ntc_status:
name: "Battery Temp Status"
internal: True
# Home Assistant sensors
# Sensores de Home Assistant
- platform: homeassistant
id: device_light_ceiling
entity_id: ${light_ceiling}
internal: true
on_value:
then:
- lvgl.label.update:
id: lbl_device_light_ceiling
text_color: !lambda |-
if (x == "on") {
return lv_color_hex(0xe6d754);
} else {
return lv_color_hex(0x626262);
}
- platform: homeassistant
id: device_light_ambient
entity_id: ${light_ambient}
internal: true
on_value:
then:
- lvgl.image.update:
id: lbl_device_light_ambient
image_recolor: !lambda |-
if (x == "on") {
return lv_color_hex(0xe6d754);
} else {
return lv_color_hex(0x626262);
}
- platform: homeassistant
id: device_cover
entity_id: ${cover}
internal: true
on_value:
then:
- lvgl.label.update:
id: lbl_device_cover
text: !lambda |-
if (x == "open") {
return "\U0000e286";
} else {
return "\U0000ec1f";
}
text_color: !lambda |-
if (x == "open") {
return lv_color_hex(0xffffff);
} else {
return lv_color_hex(0x626262);
}
- platform: homeassistant
id: device_climate
entity_id: ${climate}
internal: true
on_value:
then:
- lvgl.label.update:
id: lbl_device_climate
text_color: !lambda |-
if (x == "heat") {
return lv_color_hex(0xffae00);
} else {
return lv_color_hex(0x626262);
}
- platform: homeassistant
id: device_vacuum
entity_id: ${vacuum}
internal: true
on_value:
then:
- lvgl.image.update:
id: lbl_device_vacuum
image_recolor: !lambda |-
if (x == "docked") {
return lv_color_hex(0x626262);
} else {
return lv_color_hex(0xffffff);
}
- platform: homeassistant
id: device_tv
entity_id: ${tv}
internal: true
on_value:
then:
- lvgl.label.update:
id: lbl_device_tv
text_color: !lambda |-
if (x == "on") {
return lv_color_hex(0xe6d754);
} else {
return lv_color_hex(0x626262);
}
time:
- platform: homeassistant
id: esptime
on_time_sync:
then:
- lvgl.label.update:
id: lbl_clock
text: !lambda 'return id(esptime).now().strftime("%H:%M");'
- lvgl.label.update:
id: lbl_date
text: !lambda 'return id(esptime).now().strftime("%d/%m/%y");'
touchscreen:
- platform: cst226
id: main_touch
reset_pin: 13
on_update:
- lambda: |-
for (auto touch: touches) {
if (touch.state <= 2) {
ESP_LOGI("Touch points:", "id=%d x=%d, y=%d", touch.id, touch.x, touch.y);
}
}
#define BOARD_SENSOR_IRQ 21
#define BOARD_TOUCH_RST 13
#define BOARD_TOUCH_IRQ 7
display:
- platform: ili9xxx
id: main_display
model: ST7796
dimensions:
height: 480
width: 222
offset_height: 0
offset_width: 49
color_order: bgr
data_rate: 80MHz
cs_pin: 39
dc_pin: 9
reset_pin: 47
invert_colors: true
lvgl:
displays:
- main_display
touchscreens:
- main_touch
buffer_size: 25%
on_idle:
- timeout: ${iddle_time}
then:
- lvgl.pause:
- light.turn_off:
id: backlight
style_definitions:
- id: button_device_1
x: 10
width: 130
height: 130
pad_all: 0
translate_y: 0
bg_color: 0x000000
border_color: 0x000000
align: TOP_MID
- id: button_device_2
x: 10
width: 130
height: 130
pad_all: 0
translate_y: 110
bg_color: 0x000000
border_color: 0x000000
align: TOP_MID
- id: button_device_3
x: 10
width: 130
height: 130
pad_all: 0
translate_y: 230
bg_color: 0x000000
border_color: 0x000000
align: TOP_MID
- id: button_device_4
x: 10
width: 130
height: 130
pad_all: 0
translate_y: 350
bg_color: 0x000000
border_color: 0x000000
align: TOP_MID
- id: slider_front_cover
width: 166
height: 400
bg_color: 0x9c9c79
radius: 10
align: CENTER
- id: slider_back_cover
width: 166
height: 400
bg_color: 0xb4b4a9
radius: 10
align: CENTER
- id: slider_label_values_cover
align: CENTER
text_font: font_slider_value
text_color: 0xffffff
pages:
- id: page_home
bg_color: ${background_color}
widgets:
- image:
align: CENTER
src: logo
pad_top: 40
on_click:
then:
- lvgl.page.show: page_devices
- label:
align: TOP_LEFT
id: lbl_clock
text: "00:00"
text_color: ${text_color}
text_font: font_clock
pad_left: 5
- label:
align: TOP_LEFT
id: lbl_date
text: "00/00/00"
text_color: ${text_color_secondary}
text_font: font_date
pad_top: 45
pad_left: 5
- label:
align: TOP_RIGHT
text_font: font_status_icons
text_color: ${text_color_secondary}
text: "\U0000ebe2"
pad_right: 7
pad_top: 10
- label:
id: lbl_battery_percentage
align: TOP_RIGHT
text_font: font_home_info
text_color: ${text_color_secondary}
text: "99%"
pad_right: 30
pad_top: 5
- label:
align: TOP_MID
id: lbl_greeting1
text: "${greeting1}"
text_color: ${text_color}
text_font: font_greeting
pad_top: 110
- label:
align: TOP_MID
id: lbl_greeting2
text: "${greeting2}"
text_color: ${text_color_secondary}
text_font: font_greeting
pad_top: 130
- label:
align: CENTER
text_font: font_home_icons
text_color: 0xe6d754
text: "\U0000e430"
pad_right: 50
pad_top: 300
- label:
id: lbl_weather_temperature
align: CENTER
text_font: font_home_info
text_color: ${text_color}
text: "20 °C"
pad_left: 50
pad_top: 300
- label:
align: CENTER
text_font: font_home_icons
text_color: 0xe65476
text: "\U0000e1ff"
pad_top: 380
pad_right: 165
- label:
id: lbl_home_temperature
align: CENTER
text_font: font_home_info
text_color: ${text_color}
text: "20 °C"
pad_top: 380
pad_right: 70
- label:
align: CENTER
text_font: font_home_icons
text_color: 0x54c9e6
text: "\U0000f87e"
pad_top: 380
pad_left: 45
- label:
id: lbl_home_humidity
align: CENTER
text_font: font_home_info
text_color: ${text_color}
text: "50%"
pad_top: 380
pad_left: 130
- id: page_devices
bg_color: ${background_color}
widgets:
- obj:
styles: button_device_1
widgets:
- label:
id: lbl_device_light_ceiling
text_font: font_device_icons
text_color: ${text_color_secondary}
text: "\U0000f02a"
on_press:
then:
- homeassistant.action:
service: switch.toggle
data:
entity_id: ${light_ceiling}
- obj:
styles: button_device_2
widgets:
- image:
id: lbl_device_light_ambient
src: icon_ledstrip
image_recolor: ${text_color_secondary}
image_recolor_opa: COVER
pad_top: 12
on_press:
then:
- homeassistant.action:
service: light.toggle
data:
entity_id: ${light_ambient}
- obj:
styles: button_device_3
widgets:
- label:
id: lbl_device_cover
text_font: font_device_icons
text_color: ${text_color_secondary}
text: "\U0000e286"
on_press:
then:
lvgl.page.show: page_cover
- obj:
styles: button_device_4
widgets:
- label:
id: lbl_device_climate
text_font: font_device_icons
text_color: ${text_color_secondary}
text: "\U0000ef55"
on_press:
then:
- homeassistant.action:
service: climate.toggle
data:
entity_id: ${climate}
- id: page_devices2
bg_color: ${background_color}
widgets:
- obj:
styles: button_device_1
widgets:
- image:
id: lbl_device_vacuum
src: icon_vacuum
image_recolor: ${text_color_secondary}
image_recolor_opa: COVER
on_press:
then:
lvgl.page.show: page_vacuum
- obj:
styles: button_device_2
widgets:
- label:
id: lbl_device_tv
text_font: font_device_icons
text_color: ${text_color_secondary}
text: "\U0000e333"
on_press:
then:
- homeassistant.action:
service: media_player.turn_off
data:
entity_id: ${tv}
- obj:
styles: button_device_3
widgets:
- label:
id: lbl_device_fan
text_font: font_device_icons
text_color: ${text_color_secondary}
text: "\U0000f168"
- obj:
styles: button_device_4
widgets:
- label:
id: lbl_device_videogames
text_font: font_device_icons
text_color: ${text_color_secondary}
text: "\U0000ea28"
- id: page_cover
bg_color: ${background_color}
widgets:
- slider:
styles: slider_back_cover
id: slider_cover
indicator:
styles: slider_front_cover
knob:
opa: 0
min_value: 0
max_value: 100
on_release:
- homeassistant.action:
action: cover.set_cover_position
data:
entity_id: ${cover}
position: !lambda return int(x);
- label:
styles: slider_label_values_cover
id: slider_cover_value
text: "0%"
- label:
text_font: font_home_icons
text_color: 0xFFFFFF
text: "\U0000e286"
align: TOP_MID
pad_top: 60
- label:
text_font: font_home_icons
text_color: 0xFFFFFF
text: "\U0000ec1f"
align: BOTTOM_MID
pad_bottom: 60
- id: page_vacuum
bg_color: ${background_color}
widgets:
- obj:
styles: button_device_1
widgets:
- label:
text_font: font_device_icons
text_color: ${text_color_secondary}
text: "\U0000e1c4" # play
on_press:
then:
- homeassistant.action:
service: vacuum.start
data:
entity_id: ${vacuum}
- obj:
styles: button_device_2
widgets:
- label:
text_font: font_device_icons
text_color: ${text_color_secondary}
text: "\U0000e1a2" # pause
on_press:
then:
- homeassistant.action:
service: vacuum.pause
data:
entity_id: ${vacuum}
- obj:
styles: button_device_3
widgets:
- label:
text_font: font_device_icons
text_color: ${text_color_secondary}
text: "\U0000eaaa" # base
on_press:
then:
- homeassistant.action:
service: vacuum.return_to_base
data:
entity_id: ${vacuum}
- obj:
styles: button_device_4
widgets:
- label:
text_font: font_device_icons
text_color: ${text_color_secondary}
text: "\U0000eee1" # locate
on_press:
then:
- homeassistant.action:
service: vacuum.locate
data:
entity_id: ${vacuum}
- Cuando hayas terminado de editar el código pulsa en “Save” e “Install”. Selecciona la opción “Manual download” y espera a que el código se compile.
- Cuando termine, selecciona la opción «Factory format» para descargar el fichero ‘.bin’ correspondiente.
- Ahora ve a la página de ESPHome y pulsa en “Connect”. En la ventana emergente selecciona tu placa y pulsa en “Conectar”.
- Ahora pulsa en “Install” y selecciona el fichero ‘.bin’ obtenido en el paso 7. De nuevo, pulsa en “Install”.
- Vuelve a Home Assistant y ve a Configuración > Dispositivos y servicios. Lo normal es que tu dispositivo haya sido descubierto y aparezca en la parte superior, esperando únicamente a que pulses el botón de “Configurar”. De lo contrario pulsa en el botón de “Añadir integración”, busca “ESPHome” e introduce la IP de tu placa en el campo ‘Host’. Como siempre, te recomiendo que asignes una IP fija en tu router para evitar fallos en el futuro si esta cambia.
- Para terminar ve a Configuración > Dispositivos y servicios > ESPHome. Pulsa sobre el enlace «Configurar» correspondiente a tu dispositivo. En la ventana emergente marca la casilla «Permitir que el dispositivo realice acciones de Home Assistant» y pulsa en «Enviar». Esto va a permitir que podamos controlar nuestros dispositivos desde la pantalla.
Personalización del Habbit Remote
Vale, una vez que has terminado de integrar Lilygo T-Display S3 Pro en HA, déjame que te explique en detalle cómo funciona para que puedas personalizarlo a tu gusto.
💡 Usa estas instrucciones para replicar y adaptar la plantilla, o simplemente para observar como he construido ciertos bloques y crear tu propia plantilla.
Personalización rápida del dispositivo
En las primeras líneas del código encontrar distintos parámetros que puedes personalizar fácilmente, reemplazando su valor:
- Name. Este es el nombre que el dispositivo adopta, dentro de ESPHome (no puede contener espacios, mayúsculas, ni caracteres especiales).
- Friendly_name. Nombre «amistoso» del dispositivo, con el que aparecerá en Home Assistant.
- Logo. Ruta (externa o local) a la imagen que quieres que aparezca en la pantalla de inicio, centrada.
- Background_color. Código hexadecimal del color de fondo de la pantalla, precedido de un ‘0x’ (por ejemplo, ‘0x000000’).
- Text_color. Código hexadecimal del texto e iconos principales (reloj, sensores, etc.).
- Text_color_secondary. Código hexadecimal del texto e iconos secundarios (fecha, dispositivos inactivos, etc.).
- Iddle_time. Tiempo de inactividad tras el cual la pantalla se apaga automáticamente.
- Greeting 1. Texto que aparece en la pantalla de inicio, en la línea superior.
- Greeting 2. Texto que aparece en la pantalla de inicio, en la línea inferior.

Entidades de Home Assistant
El siguiente apartado del código muestra cómo importamos información desde las entidades de Home Assistant a nuestro dispositivo. Esto sirve para mostrar su estado en la pantalla, o para controlarlos desde la misma.
Si quieres reutilizar la plantilla, sólo tienes que reemplazarar las entidades del ejemplo por las que corresponden a tu instancia. Ahora, si quieres modificar la interfaz te sugiero que eches un vistazo a cómo las utilizo:
- Las entidades con valores numéricos (como la temperatura o la humedad), se encuentran dentro del componente ‘sensor’ del código.
- Las entidades con valores textuales (como «encendido» o «limpiando»), se encuentran dentro del componente ‘text_sensor’ del código.
Control de dispositivos
Si ya conoces cómo utilizar LVGL con ESPHome, no te costará entender como funciona la interfaz mirando el código. Si no, quizás te ayude tener en cuenta cómo la he diseñado:
- Cuando está inactivo, puedes encender la pantalla pulsando sobre ella o en cualquier de los dos botones físicos del lateral derecho.
- Cuando está activo, pulsando en los dos botones físicos puedes cambiar entre las distintas páginas de dispositivos. Puedes añadir tantas páginas como necesites.
- Pulsando en el icono de cada uno de los dispositivos, puedes alternar su estado (por ejemplo, encender/apagar una luz). Cuando cambia el estado, también cambia el color del icono para que lo identifiques a simple vista.
- En algunos casos, al pulsar sobre el icono del dispositivo, puedes acceder a otra página con más controles. Por ejemplo, el icono de las persianas lleva a un ‘slider’ para controlar el porcentaje de apertura, y el del robot aspirador permite iniciar la limpieza, pausarla o mandarlo a la estación. Todo es 100% personalizable.





Ofertas & Descuentos
Diseños 3D
Academia





























