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.
🥑 Si estás empezando con ESPHome, te recomiendo que veas el taller de la academia para sacarle el máximo partido!
Í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.
💡 Si quieres aprender cómo se configura una pantalla en ESPHome, en esta entrada te lo explico con más detalle.
- 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 😅).
Si tienes al alcance una impresora 3D puedes descargar nuestro modelo y hacerlo tú mismo. Recuerda que si eres Patreon de aguacatec tienes acceso ilimitado a todos nuestros modelos 3D, entre otras ventajas. Si ya tienes unos cuantos aguacoins en tu monedero, puedes recibirlo en casa listo para ser montado por 2 aguacoins.
¿Dudas?¿necesitas ayuda? entra aquí
Y si te ha gustado, compártelo! 🙂