Hoy te presento un genial dispositivo con el que jugar, vamos a integrar la Cheap Yellow Display en HA y a crear nuestro propio panel físico.
Índice
¿Qué es la CYD?
La ESP32-2432S028R o CYD («Cheap Yellow Display» o «pantalla amarilla y barata») para los amigos, es una placa ESP32 que trae una pantalla táctil (capacitiva) adherida de 2,8 pulgadas. Esto hace que sea el dispositivo perfecto si eres de los que no te gusta soldar, ya que sólo tienes que conectarla por su puerto USB y empezar a jugar con ella. Y lo mejor de todo es que puedes comprarla en Aliexpress por unos 10€. No necesitas nada más, ya que incluye hasta el cable USB-C.
Esto la ha convertido en un dispositivo muy popular, con una comunidad que ha creado todo tipo de proyectos a su alrededor. Como no podía ser de otra manera, hemos querido ver que podíamos sacar con ESPHome al integrar la Cheap Yellow Display en HA.
Nuestro proyecto con ESPHome
Aprovechando la pantalla de la CYD, los objetivos del proyecto son:
- Mostrar información de interés en la pantalla, así como la fecha y la hora. Si quieres comprender mejor cómo se configura el contenido de una pantalla con ESPHome, revisa esta entrada.
- Crear un menú desde el que controlar nuestros dispositivos.
- Hacer que sea fácil de instalar y totalmente personalizable.
- Diseñar una carcasa atractiva para tu nuestro dispositivo.
Configuración de ESPHome
Para integrar la Cheap Yellow Display en HA sólo tienes que seguir estos pasos:
- 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, “CYD”).
- Pulsa en “Next” y a continuación selecciona la opción “ESP32” 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 el código original y consérvalo, ya que vas a necesitar alguna parte.
esphome:
name: cyd
friendly_name: CYD
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "qT9hkJJP5sdkjlkfdsjñdsfk50JxSt21212"
ota:
- platform: esphome
password: "36992234dfdf43edbbfc98b9234428"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Cyd Fallback Hotspot"
password: "LEMTsad123"
captive_portal:
globals:
- id: show_return_page
type: bool
restore_value: yes
initial_value: "false"
# Setup a pin to control the backlight and the LED
output:
- platform: ledc
pin: GPIO21
id: 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: backlight_pwm
name: Display Backlight
id: backlight
restore_mode: ALWAYS_ON
- platform: rgb
name: LED
red: output_red
id: led
green: output_green
blue: output_blue
restore_mode: ALWAYS_OFF
# Setup SPI for the display. The ESP32-2432S028R uses separate SPI buses for display and touch
spi:
- id: tft
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
- id: touch
clk_pin: GPIO25
mosi_pin: GPIO32
miso_pin: GPIO39
touchscreen:
platform: xpt2046
spi_id: touch
cs_pin: GPIO33
interrupt_pin: GPIO36
update_interval: 50ms
threshold: 400
calibration:
x_min: 3860
x_max: 280
y_min: 340
y_max: 3860
transform:
swap_xy: false
# Create a font to use, add and remove glyphs as needed.
# Crea las fuente que quieres utilizar, añade o quita los caracteres que necesites.
font:
- file: "gfonts://Space Grotesk"
id: fecha
size: 15
- file: "gfonts://Space Grotesk"
id: hora
size: 60
- file: "gfonts://Roboto"
id: info
size: 15
- file: "gfonts://Roboto"
id: botones
size: 11
# Create the colors you want to use.
# Crea los colores que quieres utilizar.
color:
- id: black
hex: '000000'
- id: orange
hex: '16afd9'
- id: grey
hex: '464646'
# Create the icons you want to use.
# Crea los iconos que quieres utilizar.
image:
- file: mdi:home-thermometer
id: hometemperature
resize: 40x40
- file: mdi:weather-partly-cloudy
id: weather
resize: 40x40
- file: mdi:finance
id: finance
resize: 40x40
- file: mdi:heart-pulse
id: health
resize: 40x40
- file: mdi:page-previous
id: back
resize: 40x40
- file: mdi:fan
id: fan
resize: 40x40
- file: mdi:thermostat
id: thermostat
resize: 40x40
- file: mdi:robot-vacuum
id: vacuum
resize: 40x40
- file: mdi:desk-lamp
id: desk
resize: 40x40
- file: mdi:printer
id: printer
resize: 40x40
- file: mdi:printer-3d-nozzle
id: printer3d
resize: 40x40
- file: mdi:home-assistant
id: habbit
resize: 40x40
# Replace the home gif as you want.
# Reemplaza el gif the inicio como quieras.
animation:
- file: "habbit.gif"
id: ha
resize: 70x70
type: TRANSPARENT_BINARY
# This will fetch time from Home Assistant
time:
- platform: homeassistant
id: esptime
# Create sensors from HA you want to use and show.
# Crea los sensores de HA que quieres utilizar y mostrar.
sensor:
- platform: homeassistant
id: temperatura
entity_id: sensor.home_temperatura
internal: true
- platform: homeassistant
id: humedad
entity_id: sensor.home_humedad
internal: true
- platform: homeassistant
id: tempexterior
entity_id: sensor.openweathermap_temperature
internal: true
- platform: homeassistant
id: problluvia
entity_id: sensor.aemet_rain_probability
internal: true
- platform: homeassistant
id: brickken
entity_id: sensor.brickken_price
internal: true
- platform: homeassistant
id: weight
entity_id: sensor.tito_weight
internal: true
- platform: homeassistant
id: distancia
entity_id: input_number.distancia_acumulada_tito
internal: true
text_sensor:
- platform: homeassistant
id: aireacondicionado
entity_id: switch.aire_acondicionado
internal: true
- platform: homeassistant
id: calefaccion
entity_id: climate.salon
internal: true
- platform: homeassistant
id: aspirador
entity_id: vacuum.aspirador
internal: true
- platform: homeassistant
id: escritorio
entity_id: light.escritorio
internal: true
- platform: homeassistant
id: impresora
entity_id: switch.impresora
internal: true
- platform: homeassistant
id: impresora3d
entity_id: switch.impresora_3d
internal: true
# Assigns a function to each button, by calling the corresponding service in HA.
# Asigna una función a cada botón, llamando al servicio correspondiente en HA.
binary_sensor:
- platform: touchscreen
name: Button 1
x_min: 0
x_max: 140
y_min: 0
y_max: 65
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- homeassistant.service:
service: switch.toggle
data:
entity_id: switch.aire_acondicionado
- platform: touchscreen
name: Button 2
x_min: 140
x_max: 280
y_min: 0
y_max: 65
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- homeassistant.service:
service: climate.toggle
data:
entity_id: climate.salon
- platform: touchscreen
name: Button 3
x_min: 0
x_max: 140
y_min: 65
y_max: 130
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- if:
condition:
lambda: 'return id(aspirador).state == "docked";'
then:
- homeassistant.service:
service: vacuum.start
data:
entity_id: vacuum.aspirador
else:
- homeassistant.service:
service: vacuum.return_to_base
data:
entity_id: vacuum.aspirador
- platform: touchscreen
name: Button 4
x_min: 140
x_max: 280
y_min: 65
y_max: 130
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- homeassistant.service:
service: light.toggle
data:
entity_id: light.escritorio
- platform: touchscreen
name: Button 5
x_min: 0
x_max: 140
y_min: 130
y_max: 170
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- homeassistant.service:
service: switch.toggle
data:
entity_id: switch.impresora
- platform: touchscreen
name: Button 6
x_min: 140
x_max: 280
y_min: 130
y_max: 170
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
else:
- homeassistant.service:
service: switch.toggle
data:
entity_id: switch.impresora_3d
- platform: touchscreen
name: Button 7
x_min: 0
x_max: 140
y_min: 180
y_max: 260
on_press:
then:
- if:
condition:
lambda: 'return !id(show_return_page);'
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
- platform: touchscreen
name: Button 8
x_min: 140
x_max: 280
y_min: 180
y_max: 260
on_press:
then:
- globals.set:
id: show_return_page
value: !lambda "return !id(show_return_page);"
# Setup the ili9xxx platform
# Display is used as 240x320 by default so we rotate it to 90°
display:
- platform: ili9xxx
id: esp_display
model: ili9342
spi_id: tft
cs_pin: GPIO15
dc_pin: GPIO2
rotation: 90
lambda: |-
if (id(show_return_page)) {
int button_width = 100;
int button_height = 65;
int x_start = 15;
int y_start = 15;
int x_padding = 10;
int y_padding = 10;
// Define los textos para los botones
const char* button_texts[] = {
"Aire Ac",
"Calefaccion",
"Aspirador",
"Escritorio",
"Impresora",
"Impr 3D",
"Habbit",
"Regresar"
};
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 2; col++) {
int button_index = row * 2 + col;
int x = x_start + col * (button_width + x_padding);
int y = y_start + row * (button_height + y_padding);
it.rectangle(x, y, button_width, button_height, id(grey));
int text_width = strlen(button_texts[button_index]) * 5.5;
int text_height = 16;
it.print(x + (button_width - text_width) / 2, y + (button_height - text_height) / 2 + 20, id(botones), button_texts[button_index]);
}
}
if (id(aireacondicionado).state == "on") {
it.image(45, 20, id(fan), id(orange));
} else {
it.image(45, 20, id(fan), id(grey));
}
if (id(calefaccion).state == "off") {
it.image(155, 20, id(thermostat), id(grey));
} else {
it.image(155, 20, id(thermostat), id(orange));
}
if (id(aspirador).state == "docked") {
it.image(45, 95, id(vacuum), id(grey));
} else {
it.image(45, 95, id(vacuum), id(orange));
}
if (id(escritorio).state == "on") {
it.image(155, 95, id(desk), id(orange));
} else {
it.image(155, 95, id(desk), id(grey));
}
if (id(impresora).state == "on") {
it.image(45, 170, id(printer), id(orange));
} else {
it.image(45, 170, id(printer), id(grey));
}
if (id(impresora3d).state == "on") {
it.image(155, 170, id(printer3d), id(orange));
} else {
it.image(155, 170, id(printer3d), id(grey));
}
it.image(45, 245, id(habbit), id(grey));
it.image(155, 245, id(back), id(grey));
} else {
static int y = 150;
static int y_direction = 4; // Velocidad del movimiento
const int y_min = 145;
const int y_max = 155;
it.fill(id(black));
it.strftime(120, 60, id(fecha), TextAlign::CENTER, "%d/%m/%Y", id(esptime).now());
it.strftime(120, 92, id(hora), TextAlign::CENTER, "%H:%M", id(esptime).now());
it.image(120, y, id(ha), ImageAlign::CENTER);
y += y_direction;
if (y <= y_min || y >= y_max) {
y_direction = -y_direction;
}
static int current_text_index = 0;
static float text_timer = 0;
const float text_interval = 5.0; // Intervalo para cambiar el texto en segundos
text_timer += 1.0;
if (text_timer >= text_interval) {
text_timer = 0;
current_text_index = (current_text_index + 1) % 4; // Alternar entre cuatro textos
}
if (current_text_index == 0) {
it.image(15, 260, id(hometemperature), id(orange));
it.print(70, 260, id(info), "Temperatura");
it.printf(175, 260, id(info), "%.1f C", id(temperatura).state);
it.print(70, 280, id(info), "Humedad");
it.printf(175, 280, id(info), "%.1f %%", id(humedad).state);
} else if (current_text_index == 1) {
it.image(15, 260, id(weather), id(orange));
it.print(70, 260, id(info), "Temperatura");
it.printf(175, 260, id(info), "%.1f C", id(tempexterior).state);
it.print(70, 280, id(info), "Prob. Lluvia");
it.printf(175, 280, id(info), "%.1f %%", id(problluvia).state);
} else if (current_text_index == 2) {
it.image(15, 260, id(finance), id(orange));
it.print(70, 260, id(info), "Brickken");
it.printf(165, 260, id(info), "%.2f USD", id(brickken).state);
} else if (current_text_index == 3) {
it.image(15, 260, id(health), id(orange));
it.print(70, 260, id(info), "Peso");
it.printf(175, 260, id(info), "%.1f Kg", id(weight).state);
it.print(70, 280, id(info), "Distancia");
it.printf(175, 280, id(info), "%.1f Km%", id(distancia).state);
}
}
interval:
- interval: 1s
then:
animation.next_frame: ha
- Reemplázalo por el código anterior y haz las siguientes adaptaciones.
- Ajusta el ‘name’ y ‘friendly-name’ como el nombre que le quieras dar a tu asistente (por ejemplo, “CYD”).
- 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 ‘ap’.
- He comentado el código para que te resulte sencillo identificar cómo puedes modificar las fuentes, colores e iconos que se ven en pantalla.
- También puedes reemplazar el gif de la pantalla de inicio por otro que te guste más. Sólo asegúrate de guardarlo en tu la ruta ‘config/esphome/’ de tu instancia de Home Assistant.
- A continuación tendrás que indicar todas las entidades de HA que quieres utilizar en la pantalla. Indica las entidades numéricas bajo el apartado ‘sensor’ y las alfanuméricas bajo el apartado ‘text_sensor’.
- Si bajas hasta el componente ‘binary_sensor’, encontrarás cada uno de los botones del menú. Si te fijas en la parte final del código de cada botón, se utilizan los servicios a los que ya estás acostumbrado en HA. De esta forma puedes reproducir cualquier acción pulsando el botón (encender una luz, apagar un enchufe, lanzar un script…). Los botones están numerados del 1 al 7 y los puedes utilizar como quieras (puedes ver funciones de ejemplo en el código). El último botón está reservado para volver a la pantalla de inicio.
- Dentro del componente ‘display’ tendrás que definir varias cosas. En primer lugar, el texto que aparece bajo el icono de cada botón (fácil). A continuación podrás definir cuando aparece «encendido» o activo un botón. Esto es útil, por ejemplo, para saber si un dispositivo está encendido o apagado. Fíjate que debes utilizar el ‘id’ de las variables (sensores, iconos, colores) que has creado previamente en el código.
- A continuación, también dentro del componente ‘display’ puedes definir la información que se muestra en pantalla. Ajusta primero cada cuántos segundos cambia (5s por defecto). Después ajusta cuantas páginas o apartados de información quieres mostrar (4 por defecto) y personaliza la información a mostrar en cada página.
- 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 “Modern 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 Ajustes > 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’.
- Para terminar ve a Ajustes > Dispositivos y servicios > ESPHome. Pulsa sobre el enlace “Configurar” correspondiente al dispositivo de tu asistente. En la ventana emergente marca la casilla “Permitir que el dispositivo realice llamadas de servicio de Home Assistant” y pulsa en “Enviar”. Esto va a permitir que nuestra pantalla pueda controlar nuestros dispositivos.
Pónle una carcasa!
Ya hemos conseguido integrar la Cheap Yellow Display en HA, ahora sólo necesitamos un envoltorio elegante. En la comunidad de CYD puedes encontrar algunos ejemplos, aunque personalmente he preferido crear mi propio modelo. Es posible que ya lo hayas notado, pero está inspirado en el Rabbit R1 (yo le llamo Habbit 😅).

