Integrar la Cheap Yellow Display en HA

Integrar la Cheap Yellow Display en HA

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!
Esphome para novatos

¿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.

-34%
Placa de desarrollo ESP32 LVGL con WIFI y Bluetooth, módulo LCD TFT de 2,8 pulgadas, pantalla de visualización inteligente de 240x320, con panel táctil para Arduino
Aliexpress
12,65€ 19,46€
Placa de desarrollo ESP32 LVGL con WIFI y Bluetooth, módulo LCD TFT de 2,8 pulgadas, pantalla de visualización inteligente de 240x320, con panel táctil para...
ESP32 Arduino LVGL Placa de desarrollo WIFI y Bluetooth, pantalla de visualización inteligente de 2,8 pulgadas, 240x320, módulo LCD TFT de 2,8 pulgadas, con panel táctil
Aliexpress
12,78€
ESP32 Arduino LVGL Placa de desarrollo WIFI y Bluetooth, pantalla de visualización inteligente de 2,8 pulgadas, 240x320, módulo LCD TFT de 2,8 pulgadas, con...
RCmall-Pantalla de 2,8 pulgadas ESP32, ESP32-2432S028R de 240x320, LCD TFT de 2,8 pulgadas, módulo ILI9341, controlador para Arduino IoT Smart Home
Aliexpress
16,07€
RCmall-Pantalla de 2,8 pulgadas ESP32, ESP32-2432S028R de 240x320, LCD TFT de 2,8 pulgadas, módulo ILI9341, controlador para Arduino IoT Smart Home
Pantalla táctil TFT ESP32 de 2,8 pulgadas, ESP-32S, ESP-WROOM-32, ESP-32, Bluetooth, WIFI, doble núcleo, ESP32-2432S028R, controlador ILI9341
Aliexpress
17,77€
Pantalla táctil TFT ESP32 de 2,8 pulgadas, ESP-32S, ESP-WROOM-32, ESP-32, Bluetooth, WIFI, doble núcleo, ESP32-2432S028R, controlador ILI9341
*Algún precio puede haber cambiado desde la última revisión

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:

  1. 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”).
  2. 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.
  3. Pulsa en “Skip” y haz clic en el enlace “Edit” del bloque correspondiente al dispositivo que acabas de crear.
  4. 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
  1. 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.
  1. 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.
  2. Cuando termine, selecciona la opción “Modern format” para descargar el fichero ‘.bin’ correspondiente.
  3. Ahora ve a la página de ESPHome y pulsa en “Connect”. En la ventana emergente selecciona tu placa y pulsa en “Conectar”.
  4. Ahora pulsa en “Install” y selecciona el fichero ‘.bin’ obtenido en el paso 7. De nuevo, pulsa en “Install”.
  5. 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’.
  6. 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! 🙂
Contenido exclusivo de Aguacatec
Send this to a friend