Hoy os voy a contar la historia de cómo mis padres, ya mayores, tenían que subir dos tramos de escalera para acceder a la caldera de gasoil y cómo ha ido evolucionando la situación hasta el momento actual. Para poneros en contexto, mis padres viven en una casa de pueblo y lo que parecía hace años bueno, con la edad no lo es tanto. Por ello, viendo la dificultad que tienen en el proceso, le di vueltas al asunto y pretendo domotizar poco a poco su casa, según vea necesidades. En este proyecto os cuento cómo monté un soporte 3D para una pantalla táctil ESPHome que ahora usan a diario.
De la necesidad al soporte 3D
Por ello, de primeras le añadí un enchufe inteligente para poder encenderla desde la aplicación y una cámara enfocando al depósito para ver la temperatura. Esto lo hice así porque la instalación es muy antigua y no quería complicarme mucho.
Les enseñé a usar la aplicación, tanto la del enchufe como la de la cámara. Después de unos meses de uso y en otra visita posterior, aparezco con un minipc para instalar Proxmox y Home Assistant.
La idea era evitar que tengan que entrar en dos aplicaciones y uno de los problemas principales es que si uno de ellos entraba en la aplicación, el otro perdía el login y les era complicado acordarse de contraseñas y cómo entrar. No es lo mismo usar un widget que entrar en una aplicación para alguien que no ha usado móvil hasta hace poco.
Con todo montado y funcionando, les expliqué cómo entrar en Home Assistant y desde ahí tenían todo a la vista, en una sola pantalla.
Eso me llevó a un proyecto de una pantalla táctil ESP y su carcasa, y me dio la idea de montar mi propio soporte 3D para pantalla táctil ESPHome en combinación con Home Assistant, un proyecto que encontré buscando en Makerworld, una página de impresión 3D. Eso me llevó a un proyecto de una pantalla táctil ESP y su carcasa, y me dio la idea de probar este nuevo sistema, que por el momento es el que usan en combinación con Home Assistant.

Montaje de la placa ESP32 en su Soporte
A continuación os explico los pasos a seguir para su configuración y ejemplos de lo que se pueden hacer con estas pantallas. En mi caso ha sido algo sencillo, ya que por ahora solo necesitan un botón.
El enlace del proyecto de impresión 3D lo podéis encontrar en https://makerworld.com/es/models/799188-home-assistant-desk-mount-cheap-yellow-display-cyd#profileId-738906
Y en el siguiente enlace tenéis el proyecto en GitHub
https://github.com/akuehlewind/ESPHome-touch-display-mount/blob/main/esphome/home-like/cyd-2432s028/home-like.yaml
Para empezar vamos a trabajar en Home Assistant, cómo tengo montado Home Assistant en Proxmox. Si no tenéis instalado ESPHome Builder lo podéis descargar de la web oficial https://esphome.io/guides/getting_started_hassio/.
Conexión de la placa ESP32
Por otro lado, vais a necesitar conectar vuestra placa ESP32. Recomiendo compréis la que viene en el artículo; hay diferentes modelos y depende del modelo cambiarán cosas como la orientación de la pantalla.
En cuanto tengáis la placa conectada por USB, debéis detectar en qué puerto COM se encuentra. Para ello, si abrís el Administrador de dispositivos de Windows, al final del listado os aparecen todos los COM que tenéis y desenchufando y enchufando la placa veréis en qué puerto está conectado.
Este puerto lo vamos a usar cuando ingresemos en https://web.esphome.io. Aquí lo que vamos a hacer es pulsar connect, seleccionar el puerto COM donde tenemos la placa y, a continuación, pulsar en «Prepare for first use» e install.
Solo tenemos que esperar un par de minutos y ya lo tendremos listo. Añadís los datos de vuestro wifi y continuamos desde Home Assistant.
Configuración de secrets.yaml en Home Assistant
En Home Assistant, vamos a Configuración, Dispositivos y servicios, veremos nuestro nuevo dispositivo para configurar. Continuaremos configurando unos parámetros que necesitamos en el fichero secrets.yaml. Lo podéis encontrar en la ruta /homeassistant/esphome/secrets.yaml. Yo uso File Editor para modificar el fichero, que va a necesitar cuatro parámetros:
wifi_ssid: La red de tu wifi
wifi_password: La contraseña de la wifi
api_key: Podéis generar una desde la consola de Chrome. Abre la consola del navegador con F12 → pestaña Consola. Pega esto y pulsa Enter: btoa(String.fromCharCode(…crypto.getRandomValues(new Uint8Array(32))))
ota_password: Un password que generéis vosotros mismos, recomiendo uno de unos 16 dígitos.
Con estos datos ya solo tenemos que ir al menú de ESPHome Builder, seleccionar el dispositivo o tomar el control si es necesario antes. En el botón Edit os aparece un editor, que es donde debéis colocar vuestro código. Yo os adjunto el mío y os explico los parámetros básicos; en la web de GitHub encontraréis mucha más documentación.
El YAML que uso en mi instalación
Para terminar, os dejo aquí el YAML que estoy utilizando actualmente en esta instalación. No es una plantilla universal, porque cada caso cambia según la pantalla, las entidades disponibles en Home Assistant y las necesidades de cada casa, pero sí puede serviros como base para montar algo parecido.
Recordad que las claves y contraseñas es mejor dejarlas en secrets.yaml, tal y como os he comentado antes, para no exponer datos sensibles dentro del código principal.
# ============================================================
# ESPHome — ESP-SALON
# ESP32-2432S028 (Cheap Yellow Display)
# Zona izquierda: Reloj + Fecha (es) + Temperatura exterior
# Zona derecha: Botón grande "Caldera"
# ============================================================
esphome:
name: esp-salon
friendly_name: "ESP Salón"
esp32:
board: esp32dev
framework:
type: arduino
# ── WiFi ────────────────────────────────────────────────────
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
manual_ip:
static_ip: TUIP
gateway: 192.168.1.1
subnet: 255.255.255.0
ap:
ssid: "ESP-SALON Fallback"
password: "12345678"
captive_portal:
# ── Logger / API / OTA ──────────────────────────────────────
logger:
api:
encryption:
key: !secret api_encryption_key
ota:
- platform: esphome
password: !secret ota_password
# ── HTTP Request ─────────────────────────────────────────────
http_request:
useragent: esphome/esp-salon
timeout: 5s
verify_ssl: false
# ── Tiempo desde HA ──────────────────────────────────────────
time:
- platform: homeassistant
id: mi_reloj
# ── Temperatura exterior ─────────────────────────────────────
sensor:
- platform: homeassistant
id: temp_ext
entity_id: weather.forecast_casa_3
attribute: temperature
internal: false
name: "Temperatura Exterior"
on_value:
then:
- logger.log:
format: "Temp exterior recibida: %.1f"
args: [x]
- component.update: mi_display
# ── Estado de la caldera ─────────────────────────────────────
binary_sensor:
- platform: homeassistant
id: caldera_on
entity_id: switch.sonoff_100255d81f
trigger_on_initial_state: true
on_state:
then:
- component.update: mi_display
# ── SPI Bus 1: Display ───────────────────────────────────────
spi:
- id: spi_display
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
- id: spi_touch
clk_pin: GPIO25
mosi_pin: GPIO32
miso_pin: GPIO39
# ── Backlight ────────────────────────────────────────────────
output:
- platform: ledc
pin: GPIO21
id: backlight_pwm
light:
- platform: monochromatic
output: backlight_pwm
name: "Backlight Salón"
id: backlight
restore_mode: ALWAYS_ON
# ── Fuentes ──────────────────────────────────────────────────
font:
- file: "gfonts://Roboto"
id: fuente_hora
size: 42
- file: "gfonts://Roboto"
id: fuente_fecha
size: 14
- file: "gfonts://Roboto"
id: fuente_temp
size: 26
- file: "gfonts://Roboto"
id: fuente_boton
size: 28
- file: "gfonts://Roboto"
id: fuente_estado
size: 20
# ── Colores ──────────────────────────────────────────────────
color:
- id: color_blanco
red: 100%
green: 100%
blue: 100%
- id: color_gris
red: 60%
green: 60%
blue: 60%
- id: color_naranja
red: 95%
green: 61%
blue: 7%
- id: color_btn_on
red: 75%
green: 14%
blue: 17%
- id: color_btn_off
red: 10%
green: 33%
blue: 46%
- id: color_fondo_izq
red: 9%
green: 13%
blue: 24%
- id: color_linea
red: 25%
green: 25%
blue: 25%
# ── Display ──────────────────────────────────────────────────
display:
- platform: ili9xxx
id: mi_display
model: ILI9341
spi_id: spi_display
cs_pin: GPIO15
dc_pin: GPIO2
rotation: 180
invert_colors: false
data_rate: 4MHz
color_palette: 8BIT
dimensions:
width: 320
height: 240
auto_clear_enabled: false
update_interval: 30s
lambda: |-
it.fill(Color::BLACK);
int W = 320;
int H = 240;
int MID = 160;
it.filled_rectangle(0, 0, MID, H, id(color_fondo_izq));
it.vertical_line(MID, 0, H, id(color_linea));
auto t = id(mi_reloj).now();
if (t.is_valid()) {
it.strftime(MID / 2, 45, id(fuente_hora), id(color_blanco),
TextAlign::CENTER, "%H:%M", t);
const char* dias[] = {
"Domingo","Lunes","Martes","Miercoles",
"Jueves","Viernes","Sabado"
};
const char* meses[] = {
"Enero","Febrero","Marzo","Abril","Mayo","Junio",
"Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"
};
char fecha_str[32];
snprintf(fecha_str, sizeof(fecha_str), "%s %d de %s",
dias[t.day_of_week - 1],
t.day_of_month,
meses[t.month - 1]);
it.print(MID / 2, 100, id(fuente_fecha),
id(color_gris), TextAlign::CENTER, fecha_str);
}
it.print(MID / 2, 145, id(fuente_fecha),
id(color_gris), TextAlign::CENTER, "Temp. exterior");
if (!isnan(id(temp_ext).state)) {
char temp_str[10];
snprintf(temp_str, sizeof(temp_str), "%.1f C", id(temp_ext).state);
it.print(MID / 2, 170, id(fuente_temp),
id(color_naranja), TextAlign::CENTER, temp_str);
} else {
it.print(MID / 2, 170, id(fuente_temp),
id(color_gris), TextAlign::CENTER, "--.- C");
}
bool encendida = id(caldera_on).state;
auto color_btn = encendida ? id(color_btn_on) : id(color_btn_off);
it.filled_rectangle(MID + 10, 20, W - MID - 20, H - 40, color_btn);
it.rectangle(MID + 10, 20, W - MID - 20, H - 40, id(color_blanco));
it.print(MID + (W - MID) / 2, 100,
id(fuente_boton), id(color_blanco),
TextAlign::CENTER, "CALDERA");
it.print(MID + (W - MID) / 2, 150,
id(fuente_estado), id(color_blanco),
TextAlign::CENTER,
encendida ? "ON" : "OFF");
# ── Refresco periódico ───────────────────────────────────────
interval:
- interval: 2s
then:
- component.update: mi_display
# ── Touchscreen ──────────────────────────────────────────────
touchscreen:
platform: xpt2046
spi_id: spi_touch
cs_pin: GPIO33
interrupt_pin: GPIO36
update_interval: 50ms
threshold: 400
calibration:
x_min: 280
x_max: 3860
y_min: 340
y_max: 3860
transform:
swap_xy: true
mirror_x: false
mirror_y: false
on_touch:
- lambda: |-
if (touch.x > 160) {
id(toggle_caldera).execute();
}
# ── Script toggle caldera ────────────────────────────────────
script:
- id: toggle_caldera
then:
- logger.log: "Ejecutando toggle caldera via HTTP"
- http_request.post:
url: "http://TUIP:8123/api/services/switch/toggle"
request_headers:
Authorization: !secret ha_bearer_token
Content-Type: "application/json"
body: '{"entity_id": "switch.sonoff_100255d81f"}'
- delay: 500ms
- component.update: mi_display
En mi caso, con esta configuración ya tenemos una solución práctica y sencilla para el día a día. A partir de aquí, cada uno puede ampliarla con más botones, sensores, páginas o automatizaciones según lo que necesite.


