En esta entrada te enseño a integrar el reTerminal E1001 en HA, la mejor pantalla de tinta electrónica que hemos probado hasta ahora.
Índice
reTerminal E1001, ¿la pantalla perfecta?
La reTerminal E1001 es la nueva pantalla de tinta electrónica lanzada por Seeed Studio, distribuidor oficial de productos de Home Assistant y un fabricante bien conocido en nuestra comunidad por productos como el ReSpeaker Lite.
Se trata de una pantalla en blanco y negro de 7,5″ (800 × 480 pixel), que corre sobre una placa ESP32-S3 con 8 MB de PSRAM (compatible con ESPHome) y tiene una batería incorporada de 2000 mAh (que proporciona hasta 3 meses de autonomía, actualizándose cada 6h). Además admite tarjetas Micro SD de hasta 32GB e incorpora conectividad WiFi y Bluetooth, botones físicos, sensor de temperatura y humedad, micrófono, zumbador y un puerto de expansión (puedes consultar el ‘pinout’ en su documentación oficial).

Sólo por los componentes que incorpora, ya se trata de un dispositivo superior al resto de pantallas de tinta electrónica que hemos probado hasta ahora. Pero además de esto hay otros dos aspectos que la desmarcan de otras pantallas.
El primero es que se nota un acabado premium con una carcasa metálica, que viene preparada para ser colgada en la pared o colocada en tu escritorio con el pie que incluye el pack. Por lo tanto, puedes usarla desde que la sacas de la caja (sin necesidad de comprar marcos o una carcasa impresa en 3D).

El segundo es que, como exploraremos a continuación, es una pantalla «open-source» muy fácil de utilizar y compatible con distintas plataformas (incluyendo, por supuesto, Home Assistant). Mientras que el resto de pantallas que hemos usado hasta ahora requerían ciertos conocimientos de ESPHome, esta pantalla puede ser utilizada aunque no tengas ni idea de programación.

Plataformas compatibles
Antes de integrar el reTerminal E1001 en HA quiero resaltar que esta pantalla de tinta electrónica también es compatible con otras plataformas. Por ejemplo, si vienes del mundo de Arduino, podrás usar tus conocimientos con ella.
Otra opción muy interesante es utilizar TRMNL, una plataforma muy popular que te permite instalar plugins para mostrar, de una forma muy sencilla, información de tus aplicaciones (calendarios, estadísticas, cotizaciones, listas de tareas… y mucho más).

No obstante, si no quieres pagar ni programar, te recomiendo que utilices SenseCraft HMI. Esta es una plataforma desarrollada por Seeed Studio, que te permite personalizar tu pantalla a través de una interfaz gráfica (sin necesidad de escribir ni una línea de código).
Puedes consultar las instrucciones en la documentación oficial, pero para empezar sólo necesitas seguir estos pasos:
- Enciende la pantalla con el interruptor que trae en la parte posterior y en pantalla verás el nombre del punto de acceso WiFi que ha creado. Utiliza tu dispositivo móvil para conectarte a dicho punto y conéctala a tu red WiFi. Como siempre, te recomiendo que asignes una IP fija en tu router para evitar fallos en el futuro si esta cambia.
- Cuando se conecte a la red aparecerá en pantalla un código de emparejamiento. Entra en la página de SenseCraft HMI y baja hasta encontrar el botón «Connect Device». Al pulsar sobre el botón tendrás que hacer login con tu cuenta de Seeed Studio, o crear una nueva.

- Desde la interfaz principal, pulsa sobre «+ Add Device», dale un nombre a tu pantalla (por ejemplo, «reTerminal E1001») e introduce el código de emparejamiento que aparece en el dispositivo. Inmediatamente el dispositivo quedará conectado emitiendo un sonido.
- Si ahora pulsas sobre tu pantalla, accederás a la interfaz de configuración. Fíjate que cargar imágenes en tu pantalla es tan fácil como pulsar en «Add Photos», seleccionar un fichero y hacer clic en «Deploy».

- Puedes añadir más imágenes y establecer un intervalo de tiempo (en el campo «Interval»), o cambiar de una a otra utilizando los botones de la parte superior del marco.
- También puedes crear una interfaz pulsando en el apartado «Canvas» del menú lateral. Esto abrirá un sencillo panel, al puro estilo «paint» para que la diseñes dibujando formas, textos, imágenes, códigos QR… Y si te da pereza, puedes pedirle a la IA que lo haga por ti! (por ejemplo, «Create a dashboard for my Smart Home»), o utilizar alguno de los «templates» que vienen listos para usar (como el del tiempo de tu ciudad).
- Además puedes añadir distintas páginas (pulsando sobre «+ Add Page» en la parte superior), para que vaya cambiando la información de tu pantalla. También me ha gustado mucho que tengas la posibilidad de exportar e importar tus diseños a un fichero JSON, para poder compartirlos.
Integración con Home Assistant
Ahora si, vamos a integrar el reTerminal E1001 en HA y para ello te voy a enseñar dos formas de hacerlo.
Integración con Puppet
Puppet es un complemento de Home Assistant que, básicamente, hace una captura de pantalla de un dashboard y la expone como imagen para que la puedas mostrar en tu ‘display’.
Esto tiene como gran ventaja que no requiere que diseñes la pantalla programando en ESPHome, ya que puedes hacerlo creando tarjetas desde el panel de control. Siguiendo las instrucciones de la documentación oficial, puedes integrar el reTerminal E1001 en HA de la siguiente forma:
- En Home Assistant, haz clic sobre tu nombre de usuario y ve al apartado «Seguridad». Baja hasta el final de la pantalla y haz clic en «Crear token». Dale un nombre (por ejemplo, «Puppet») y consérvalo.
- Pulsa en este enlace para instalar el complemento Puppet. Se abrirá una nueva ventana y primero tendrás que añadir el repositorio, para luego pulsar en «Instalar».
- Cuando finalice la instalación accede a la pestaña de «Configuración», copia el token generado en el primer paso en el campo «access_token» y pulsa en «Guardar»
- Vuelve a la pestaña de «Información», pulsa en «Iniciar» y accede a la pestaña «Registro» para verificar que se ha iniciado sin errores. En la consola verás que el complemento te expone esta URL que corresponde a la imagen de tu panel de control.
http://homeassistant.local:10000/lovelace/0?viewport=1000x1000
- Observa que puedes modificar la URL de la siguiente forma, para adaptar la resolución y mostrarla en blanco y negro (adaptándola las características de nuestra pantalla). También puedes reemplazar el ‘0’ por la ruta al panel que quieres mostrar.
http://homeassistant.local:10000/lovelace/0?viewport=800x480&eink=2
- En Home Assistant, ve a tu complemento de ESPHome, pulsa en “New device” y “Continue”. Dale un nombre a tu dispositivo (por ejemplo, “reTerminal 1001”) y pulsa en “Next”.
- Como tipo de dispositivo selecciona “ESP32-S3”. Observarás de fondo que se ha creado un nuevo bloque para tu dispositivo. Pulsa en “Skip” y haz clic en “Edit”, sobre el bloque de tu dispositivo.
- Añade las siguientes líneas al final de tu código, adaptando la URL si fuera necesario:
http_request:
verify_ssl: false
timeout: 10s
watchdog_timeout: 15s
online_image:
- id: dashboard_image
format: PNG
type: BINARY
buffer_size: 30000
url: http://homeassistant.local:10000/lovelace/0?viewport=800x480&eink=2&invert # Replace with your Home Assistant address
update_interval: 30s
on_download_finished:
- delay: 0ms
- component.update: epaper_display
spi:
clk_pin: GPIO7
mosi_pin: GPIO9
display:
- platform: waveshare_epaper
id: epaper_display
model: 7.50inv2
cs_pin: GPIO10
dc_pin: GPIO11
reset_pin:
number: GPIO12
inverted: false
busy_pin:
number: GPIO13
inverted: true
update_interval: never
lambda: |-
it.image(0, 0, id(dashboard_image));
- Pulsa en “Save” e “Install”. Selecciona “Manual download” y espera a que se compile el código. Cuando termine, selecciona la opción “Modern format” para descargar el fichero ‘.bin’ correspondiente.
- Conecta la pantalla con el cable USB-C de datos por el puerto que trae en un lateral a tu ordenador. Ahora ve a la página de ESPHome y pulsa en “Connect”. En la ventana emergente selecciona tu dispositivo y pulsa en “Conectar”.
- Ahora pulsa en “Install” y selecciona el fichero ‘.bin’ obtenido en el paso 9. 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’.

Integración con ESPHome
Por último voy a enseñarte cómo integrar el reTerminal E1001 en HA aprovechándo todo su potencial. Es cierto, es un poco más laborioso, pero tratándose de la pantalla de tinta electrónica que hemos tenido, no podíamos desarpovecharla.
Basándonos en la documentación oficial, he creado un código que te permite lo siguiente:
- Leer el sensor de temperatura y humedad, tanto desde la pantalla como desde HA.
- Monitorizar la batería del dispositivo, tanto desde la pantalla como desde HA.
- Crear rápidamente una interfaz de bienvenida con fecha, hora y una imagen personalizable (sin programar).
- Crear distintas páginas y navegar entre ellas usando los botones blancos.
- Configurar fácilmente el ‘sleep mode’ para maximizar la duración de la batería.
- Actualizar manualmente el contenido de la pantalla usando el botón verde.
- Tener control absoluto del zumbador, para emitir politonos desde Home Assistant.
- Además puedes usar los ejemplos como base para personalizar tu pantalla.

Si quieres decantarte por esta opción, 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, “reTerminal 1001”).
- 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 “Edit”, sobre el bloque de tu dispositivo. Copia el código que aparece y consérvalo, ya que vas a necesitar alguna parte del mismo.
- Copia el siguiente código y úsalo para reemplazar el código anterior en ESPHome.
substitutions:
# Device customization
# Personalización del dispositivo
name: reterminal-1001
friendly_name: reTerminal 1001
main_image: https://aguacatec.es/wp-content/uploads/2025/09/terminal.png
pages: 3
# Sleep mode configuration
# Configuración del sleep mode
run_duration: 60s
sleep_duration: 3600s
# Examples
# Ejemplos
weather: weather.openweathermap
calendar: calendar.aguacatec
telegram_members: input_number.miembros_telegram
################################################################################################################
esphome:
name: ${name}
friendly_name: ${friendly_name}
on_boot:
priority: 600
then:
- output.turn_on: bsp_battery_enable
- delay: 200ms
- component.update: battery_voltage
- component.update: battery_level
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "asdfsdsdf6465/aeT/Y+1slF9CIaknds9823"
services:
- service: play_rtttl
variables:
song_str: string
then:
- rtttl.play:
rtttl: !lambda 'return song_str;'
ota:
- platform: esphome
password: "sdds6451637e461003derdfg456f"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Reterminal-1001 Fallback Hotspot"
password: "KGdsajtyYTEv76Ka"
captive_portal:
binary_sensor:
- platform: gpio
pin:
number: GPIO4
mode: INPUT_PULLUP
inverted: true
id: button_2
name: "Page - Next"
icon: "mdi:page-next"
internal: true
on_press:
then:
- lambda: |-
if (id(page_index) == ${pages}-1 ) {
id(page_index) = 0;
}
else {
id(page_index) += 1;
}
id(epaper_display).update();
- platform: gpio
pin:
number: GPIO5
mode: INPUT_PULLUP
inverted: true
id: button_3
name: "Page - Previous"
icon: "mdi:page-previous"
internal: true
on_press:
then:
- lambda: |-
if (id(page_index) == 0) {
id(page_index) = ${pages}-1;
}
else {
id(page_index) -= 1;
}
id(epaper_display).update();
deep_sleep:
id: deep_sleep_1
run_duration: ${run_duration}
sleep_duration: ${sleep_duration}
wakeup_pin: GPIO3
wakeup_pin_mode: INVERT_WAKEUP
font:
- file: "gfonts://Kanit"
id: font_time
size: 60
- file: "gfonts://Kanit"
id: font_date
size: 20
- file: "gfonts://Inter@700"
id: font_sensor
size: 22
- file: "gfonts://Kanit"
id: font_temperature
size: 100
- file: "gfonts://Exo 2"
id: font_calendar
size: 25
- file: "gfonts://Story Script"
id: font_telegram_title
size: 45
- file: "gfonts://Kanit"
id: font_telegram
size: 200
globals:
- id: page_index
type: int
restore_value: no
initial_value: '0'
i2c:
scl: GPIO20
sda: GPIO19
image:
- file: mdi:battery-70
id: icon_battery
resize: 25x25
type: BINARY
transparency: chroma_key
- file: mdi:thermometer
id: icon_temperature
resize: 25x25
type: BINARY
transparency: chroma_key
- file: mdi:water-percent
id: icon_humidity
resize: 25x25
type: BINARY
transparency: chroma_key
- file: ${main_image}
id: wallpaper
resize: 800x458
type: BINARY
transparency: chroma_key
invert_alpha: true
- file: https://aguacatec.es/wp-content/uploads/2025/09/Telegram2.png
id: telegram
resize: 400x400
type: BINARY
transparency: chroma_key
invert_alpha: true
- file: mdi:weather-partly-cloudy
id: icon_weather
resize: 200x200
type: BINARY
transparency: chroma_key
- file: mdi:calendar
id: icon_calendar
resize: 200x200
type: BINARY
transparency: chroma_key
output:
- platform: ledc
pin: GPIO45
id: rtttl_out
frequency: 1000Hz
- platform: gpio
pin: GPIO21
id: bsp_battery_enable
rtttl:
output: rtttl_out
id: my_rtttl
gain: 60%
sensor:
- platform: sht4x
temperature:
name: "Temperature"
id: temp_sensor
humidity:
name: "Humidity"
id: hum_sensor
- platform: adc
pin: GPIO1
name: "Battery Voltage"
id: battery_voltage
internal: true
attenuation: 12db
filters:
- multiply: 2.0 # Voltage divider compensation
- platform: template
name: "Battery Level"
id: battery_level
unit_of_measurement: "%"
icon: "mdi:battery"
device_class: battery
state_class: measurement
lambda: 'return id(battery_voltage).state;'
filters:
- calibrate_linear:
- 4.15 -> 100.0
- 3.96 -> 90.0
- 3.91 -> 80.0
- 3.85 -> 70.0
- 3.80 -> 60.0
- 3.75 -> 50.0
- 3.68 -> 40.0
- 3.58 -> 30.0
- 3.49 -> 20.0
- 3.41 -> 10.0
- 3.30 -> 5.0
- 3.27 -> 0.0
- clamp:
min_value: 0
max_value: 100
- platform: homeassistant
id: telegram_members_counter
entity_id: ${telegram_members}
internal: true
- platform: homeassistant
id: sensor_temperature
entity_id: ${weather}
attribute: temperature
internal: true
spi:
clk_pin: GPIO7
mosi_pin: GPIO9
text_sensor:
- platform: homeassistant
id: calendar_event
entity_id: ${calendar}
attribute: message
internal: true
- platform: homeassistant
id: calendar_time
entity_id: ${calendar}
attribute: start_time
internal: true
time:
- platform: homeassistant
id: ha_time
display:
- platform: waveshare_epaper
id: epaper_display
model: 7.50inv2
cs_pin: GPIO10
dc_pin: GPIO11
reset_pin:
number: GPIO12
inverted: false
busy_pin:
number: GPIO13
inverted: true
update_interval: 30s
lambda: |-
// DATE AND TIME
auto now = id(ha_time).now();
struct tm timeinfo = now.to_c_tm();
// centering time HH:MM
char timeStr[6];
strftime(timeStr, sizeof(timeStr), "%H:%M", &timeinfo);
it.printf(80, 40, id(font_time), TextAlign::CENTER, timeStr);
// Date: Day of week
// const char *weekday[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
const char *weekday[] = {"Dom","Lun","Mar","Mie","Jue","Vie","Sab"};
const char *wday = weekday[timeinfo.tm_wday];
// Date: month - day
char dateStr[12];
strftime(dateStr, sizeof(dateStr), "%b %d", &timeinfo); // e.g. Jun 15
// Day of the week + date below the time
it.printf(80, 80, id(font_date), TextAlign::CENTER, "%s, %s", wday, dateStr);
// SENSOR AND BATTERY LEVEL
it.image(520, 15, id(icon_temperature));
it.printf(550, 14, id(font_sensor), "%.0f°C", id(temp_sensor).state);
it.image(620, 15, id(icon_humidity));
it.printf(650, 14, id(font_sensor), "%.0f%%", id(hum_sensor).state);
it.image(710, 15, id(icon_battery));
it.printf(735, 14, id(font_sensor), "%.0f%%", id(battery_level).state);
// ---------- PAGE 0 ----------
if (id(page_index) == 0) {
// WALLPAPER
it.image(0, 20, id(wallpaper));
}
// ---------- PAGE 1 ----------
if (id(page_index) == 1) {
it.image(120, 130, id(icon_weather));
it.printf(220, 360, id(font_temperature), TextAlign::CENTER, "%.0f°C", id(sensor_temperature).state);
it.image(460, 130, id(icon_calendar));
it.printf(560, 350, id(font_sensor), TextAlign::CENTER, "%s", id(calendar_event).state.c_str());
it.printf(560, 380, id(font_calendar), TextAlign::CENTER, "%s", id(calendar_time).state.c_str());
}
// ---------- PAGE 2 ----------
if (id(page_index) == 2) {
it.printf(280, 240, id(font_telegram), TextAlign::CENTER, "%.0f", id(telegram_members_counter).state);
it.print(280, 350, id(font_telegram_title), TextAlign::CENTER, "Miembros en Telegram");
it.image(400, 50, id(telegram));
}
- Parte importante. En este código no se han incluido las credenciales para que el dispositivo se conecte a tu WiFi y tu instancia de Home Assistant y tienes que incluirlas manualmente. En concreto me refiero a las siguientes líneas del código que has copiado en el paso 4.
# Enable Home Assistant API
api:
encryption:
key: "bg6hash6sjdjsdjk02hh0qnQeYVwm123vdfKE8BP5"
ota:
- platform: esphome
password: "asddasda27aab65a48484502b332f"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Assist Fallback Hotspot"
password: "ZsasdasdHGP2234"
- Lo que tienes que hacer es, encontrar las líneas correspondientes en el código (está al principio) y añadir la información correspondiente.
- Ahora sí, pulsa en “Save” e “Install”. Selecciona “Manual download” y espera a que se compile el código.
- Cuando termine, selecciona la opción “Modern format” para descargar el fichero ‘.bin’ correspondiente.
- Conecta el reTerminal 1001 con el cable USB-C de datos por el puerto que trae en la parte posterior a tu ordenador.
- 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 9. 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’.
- Listo! Ya has terminado de integrar el reTerminal E1001 en HA y puedes consultar las siguientes entidades!

Trucos para un dashboard bonito
Con esta pantalla lo tienes todo para tener un panel perfectamente integrado con tu decoración. A continuación te dejo unos trucos y otro código de ejemplo para ayudarte a conseguirlo:
- Combínalo con un marco de madera, como el RÖDALM de Ikea (por menos de 4€).
- Utiliza alguno de los tipos de letra elegantes disponibles en Google Fonts.
- Genera una imagen con IA, y pídele que la convierta a blanco y negro, con una escala de grises de 8 colores.
- Activa el parámetro ‘dither: FLOYDSTEINBERG’ en la imagen de ESPHome, para conseguir mayor resolución.
substitutions:
# Device customization
# Personalización del dispositivo
name: reterminal-1001
friendly_name: reTerminal 1001
# Sleep mode configuration
# Configuración del sleep mode
run_duration: 60s
sleep_duration: 21600s # 6 Horas
# Sensors
# Sensores
weather: weather.openweathermap
youtube_members: sensor.aguacatec_suscriptores
telegram_members: sensor.seguidores_aguacatec
calendar_events: calendar.aguacatec
calendar_sessions: calendar.aguacatec_sesiones
# Other settings
# Otros ajustes
allowed_characters: " ¿?¡!#%'()+,-./:µ³°0123456789ABCDEFGHIJKLMNOPQRSTUVWYZabcdefghijklmnopqrstuvwxyzáéíóú"
################################################################################################################
esphome:
name: ${name}
friendly_name: ${friendly_name}
on_boot:
priority: 600
then:
- delay: 2000ms
- output.turn_on: bsp_battery_enable
- delay: 200ms
- component.update: battery_voltage
- component.update: battery_level
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "ZTRnKpsdf423T/Y+1slF9dsf43"
ota:
- platform: esphome
password: "sdf431637e46100324dsf"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Reterminal-1001 Fallback Hotspot"
password: "KGwWdsaads6TKa"
captive_portal:
binary_sensor:
- platform: gpio
pin:
number: GPIO4
mode: INPUT_PULLUP
inverted: true
id: button_2
name: "Page - Next"
icon: "mdi:page-next"
internal: true
- platform: gpio
pin:
number: GPIO5
mode: INPUT_PULLUP
inverted: true
id: button_3
name: "Page - Previous"
icon: "mdi:page-previous"
internal: true
deep_sleep:
id: deep_sleep_1
run_duration: ${run_duration}
sleep_duration: ${sleep_duration}
wakeup_pin: GPIO3
wakeup_pin_mode: INVERT_WAKEUP
font:
- file: "gfonts://Kanit"
id: font_topbar
size: 20
glyphs: ${allowed_characters}
- file: "gfonts://Lobster"
id: font_quote
size: 40
glyphs: ${allowed_characters}
- file: "gfonts://Inter@700"
id: font_event
size: 22
glyphs: ${allowed_characters}
- file: "gfonts://Inter@700"
id: font_start_time
size: 18
glyphs: ${allowed_characters}
i2c:
scl: GPIO20
sda: GPIO19
image:
# TOPBAR
- file: mdi:white-balance-sunny
id: icon_weather
resize: 25x25
type: BINARY
transparency: chroma_key
- file: mdi:battery-70
id: icon_battery
resize: 25x25
type: BINARY
transparency: chroma_key
- file: mdi:thermometer
id: icon_temperature
resize: 25x25
type: BINARY
transparency: chroma_key
- file: mdi:water-percent
id: icon_humidity
resize: 25x25
type: BINARY
transparency: chroma_key
# CALENDAR
- file: mdi:calendar
id: icon_calendar
resize: 50x50
type: BINARY
transparency: chroma_key
- file: mdi:forum-outline
id: icon_session
resize: 45x45
type: BINARY
transparency: chroma_key
# SOCIAL COUNTER
- file: mdi:youtube
id: icon_youtube
resize: 35x35
type: BINARY
transparency: chroma_key
- file: images/telegram_icon.png
id: icon_telegram
resize: 25x25
type: BINARY
transparency: chroma_key
# IMAGE
- file: images/prueba.png
id: wallpaper
resize: 500x500
type: BINARY
dither: FLOYDSTEINBERG
transparency: chroma_key
output:
- platform: gpio
pin: GPIO21
id: bsp_battery_enable
sensor:
- platform: sht4x
temperature:
name: "Temperature"
id: temp_sensor
humidity:
name: "Humidity"
id: hum_sensor
- platform: adc
pin: GPIO1
name: "Battery Voltage"
id: battery_voltage
internal: true
attenuation: 12db
filters:
- multiply: 2.0 # Voltage divider compensation
- platform: template
name: "Battery Level"
id: battery_level
unit_of_measurement: "%"
icon: "mdi:battery"
device_class: battery
state_class: measurement
lambda: 'return id(battery_voltage).state;'
filters:
- calibrate_linear:
- 4.15 -> 100.0
- 3.96 -> 90.0
- 3.91 -> 80.0
- 3.85 -> 70.0
- 3.80 -> 60.0
- 3.75 -> 50.0
- 3.68 -> 40.0
- 3.58 -> 30.0
- 3.49 -> 20.0
- 3.41 -> 10.0
- 3.30 -> 5.0
- 3.27 -> 0.0
- clamp:
min_value: 0
max_value: 100
- platform: homeassistant
id: sensor_temperature
entity_id: ${weather}
attribute: temperature
internal: true
- platform: homeassistant
id: counter_youtube_subscribers
entity_id: ${youtube_members}
internal: true
- platform: homeassistant
id: counter_telegram_members
entity_id: ${telegram_members}
internal: true
spi:
clk_pin: GPIO7
mosi_pin: GPIO9
text_sensor:
- platform: homeassistant
id: calendar_event_title
entity_id: ${calendar_events}
attribute: message
internal: true
- platform: homeassistant
id: calendar_event_time
entity_id: ${calendar_events}
attribute: start_time
internal: true
- platform: homeassistant
id: calendar_session_title
entity_id: ${calendar_sessions}
attribute: message
internal: true
- platform: homeassistant
id: calendar_session_time
entity_id: ${calendar_sessions}
attribute: start_time
internal: true
time:
- platform: homeassistant
id: ha_time
display:
- platform: waveshare_epaper
id: epaper_display
model: 7.50inv2
cs_pin: GPIO10
dc_pin: GPIO11
reset_pin:
number: GPIO12
inverted: false
busy_pin:
number: GPIO13
inverted: true
update_interval: 30s
lambda: |-
// DATE AND WEATHER
auto now = id(ha_time).now();
struct tm timeinfo = now.to_c_tm();
// Date: Day of week
// const char *weekday[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
const char *weekday[] = {"Dom","Lun","Mar","Mie","Jue","Vie","Sab"};
const char *wday = weekday[timeinfo.tm_wday];
// Date: month - day
char dateStr[12];
strftime(dateStr, sizeof(dateStr), "%d %b", &timeinfo); // e.g. Jun 15
// Day of the week + date below the time
it.printf(60, 22, id(font_topbar), TextAlign::CENTER, "%s, %s", wday, dateStr);
it.image(120, 10, id(icon_weather));
it.printf(170, 22, id(font_topbar), TextAlign::CENTER, "%.0f°C", id(sensor_temperature).state);
// SENSOR AND BATTERY LEVEL
it.image(585, 10, id(icon_temperature));
it.printf(610, 8, id(font_topbar), "%.0f°C", id(temp_sensor).state);
it.image(658, 10, id(icon_humidity));
it.printf(683, 8, id(font_topbar), "%.0f%%", id(hum_sensor).state);
it.image(725, 10, id(icon_battery));
it.printf(750, 8, id(font_topbar), "%.0f%%", id(battery_level).state);
// QUOTE
it.printf(10, 60, id(font_quote), "Si puedes imaginarlo, puedes crearlo");
// CALENDAR
it.image(10, 210, id(icon_calendar));
// Función para limpiar caracteres especiales
std::string clean_string = id(calendar_event_title).state;
std::string cleaned = "";
for (char c : clean_string) {
unsigned char uc = c;
if ((uc >= 32 && uc <= 126) || // ASCII imprimibles básicos
uc == 225 || // á
uc == 233 || // é
uc == 237 || // í
uc == 243 || // ó
uc == 250 || // ú (ASCII extendido)
uc == 195 || // Primer byte UTF-8 para acentos
uc == 161 || uc == 169 || uc == 173 || uc == 179 || uc == 186) { // Segundos bytes UTF-8
cleaned += c;
}
}
it.printf(70, 208, id(font_event), "%s", cleaned.c_str());
if (id(calendar_event_time).state.length() >= 16) {
std::string date = id(calendar_event_time).state.substr(8, 2); // Extrae el día (DD)
std::string month = id(calendar_event_time).state.substr(5, 2); // Extrae el mes (MM)
std::string hour = id(calendar_event_time).state.substr(11, 2); // Extrae la hora (HH)
std::string minute = id(calendar_event_time).state.substr(14, 2); // Extrae los minutos (MM)
// Mapea el número del mes a su nombre en español
std::map<std::string, std::string> month_names = {
{"01", "enero"}, {"02", "febrero"}, {"03", "marzo"}, {"04", "abril"},
{"05", "mayo"}, {"06", "junio"}, {"07", "julio"}, {"08", "agosto"},
{"09", "septiembre"}, {"10", "octubre"}, {"11", "noviembre"}, {"12", "diciembre"}
};
std::string month_name = month_names[month]; // Obtiene el nombre del mes
// Imprime el evento y la fecha formateada
it.printf(75, 233, id(font_start_time), "%s de %s, a las %s:%sh", date.c_str(), month_name.c_str(), hour.c_str(), minute.c_str());
}
// SESSION
it.image(13, 290, id(icon_session));
// Función para limpiar caracteres especiales
std::string clean_string_s = id(calendar_session_title).state;
std::string cleaned_s = "";
for (char c : clean_string_s) {
unsigned char uc = c;
if ((uc >= 32 && uc <= 126) || // ASCII imprimibles básicos
uc == 225 || // á
uc == 233 || // é
uc == 237 || // í
uc == 243 || // ó
uc == 250 || // ú (ASCII extendido)
uc == 195 || // Primer byte UTF-8 para acentos
uc == 161 || uc == 169 || uc == 173 || uc == 179 || uc == 186) { // Segundos bytes UTF-8
cleaned_s += c;
}
}
it.printf(72, 288, id(font_event), "%s", cleaned_s.c_str());
if (id(calendar_session_time).state.length() >= 16) {
std::string date = id(calendar_session_time).state.substr(8, 2); // Extrae el día (DD)
std::string month = id(calendar_session_time).state.substr(5, 2); // Extrae el mes (MM)
std::string hour = id(calendar_session_time).state.substr(11, 2); // Extrae la hora (HH)
std::string minute = id(calendar_session_time).state.substr(14, 2); // Extrae los minutos (MM)
// Mapea el número del mes a su nombre en español
std::map<std::string, std::string> month_names = {
{"01", "enero"}, {"02", "febrero"}, {"03", "marzo"}, {"04", "abril"},
{"05", "mayo"}, {"06", "junio"}, {"07", "julio"}, {"08", "agosto"},
{"09", "septiembre"}, {"10", "octubre"}, {"11", "noviembre"}, {"12", "diciembre"}
};
std::string month_name = month_names[month]; // Obtiene el nombre del mes
// Imprime el evento y la fecha formateada
it.printf(75, 313, id(font_start_time), "%s de %s, a las %s:%sh", date.c_str(), month_name.c_str(), hour.c_str(), minute.c_str());
}
// SOCIAL COUNTER
it.image(10, 440, id(icon_youtube));
it.printf(52, 442, id(font_topbar), "%.0f Suscriptores", id(counter_youtube_subscribers).state);
it.image(220, 445, id(icon_telegram));
it.printf(252, 442, id(font_topbar), "%.0f Miembros", id(counter_telegram_members).state);
// IMAGE
it.image(360, 30, id(wallpaper));
Mapa estelar inteligente
Y si has visto este video y quieres convertirlo en un mapa estelar inteligente, a continuación te dejo todo el código de ESPHome.
substitutions:
# Device customization
# Personalización del dispositivo
name: mapa-estelar
friendly_name: Mapa Estelar
star_chart: images/star_chart.png
quote: 'Así empezó todo'
event: '25 de Marzo de 2021 · Madrid'
event_year: 2021
event_month: 3
event_day: 25
calendar: calendar.aguacatec
# Sleep mode configuration
# Configuración del sleep mode
run_duration: 60s
sleep_duration: 43200s # 12 Hours / Horas
# Other settings
# Otros ajustes
allowed_characters: " ·¿?¡!#%'()+,-./:µ³°0123456789ABCDEFGHIJKLMNOPQRSTUVWYZabcdefghijklmnñopqrstuvwxyzáéíóú"
################################################################################################################
esphome:
name: ${name}
friendly_name: ${friendly_name}
on_boot:
priority: 600
then:
- delay: 2000ms
- output.turn_on: bsp_battery_enable
- delay: 200ms
- component.update: battery_voltage
- component.update: battery_level
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "NheSBtt07y2sqb8ir47krVoRZKTbPyNxtO6KCm0a2bs="
ota:
- platform: esphome
password: "18f3570ad68c9140294940c9a395f65e"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Mapa-Estelar Fallback Hotspot"
password: "x57ndOAjd7d5"
captive_portal:
deep_sleep:
id: deep_sleep_1
run_duration: ${run_duration}
sleep_duration: ${sleep_duration}
wakeup_pin: GPIO3
wakeup_pin_mode: INVERT_WAKEUP
font:
- file: "gfonts://Lobster"
id: font_quote
size: 40
glyphs: ${allowed_characters}
- file: "gfonts://Inter@700"
id: font_event
size: 16
glyphs: ${allowed_characters}
- file: "gfonts://Inter@700"
id: font_count
size: 12
glyphs: ${allowed_characters}
- file: "gfonts://Inter@700"
id: font_current_date
size: 25
glyphs: ${allowed_characters}
- file: "gfonts://Inter@700"
id: font_calendar_event
size: 20
glyphs: ${allowed_characters}
- file: "gfonts://Inter@700"
id: font_calendar_time
size: 16
glyphs: ${allowed_characters}
- file: "gfonts://Kanit"
id: font_battery_level
size: 20
glyphs: ${allowed_characters}
i2c:
scl: GPIO20
sda: GPIO19
image:
# Star chart
# Mapa estelar
- file: ${star_chart}
id: star_chart
resize: 400x400
type: BINARY
dither: FLOYDSTEINBERG
transparency: chroma_key
invert_alpha: false
- file: mdi:battery-70
id: icon_battery
resize: 25x25
type: BINARY
transparency: chroma_key
output:
- platform: gpio
pin: GPIO21
id: bsp_battery_enable
sensor:
- platform: adc
pin: GPIO1
name: "Battery Voltage"
id: battery_voltage
internal: true
attenuation: 12db
filters:
- multiply: 2.0 # Voltage divider compensation
- platform: template
name: "Battery Level"
id: battery_level
unit_of_measurement: "%"
icon: "mdi:battery"
device_class: battery
state_class: measurement
lambda: 'return id(battery_voltage).state;'
filters:
- calibrate_linear:
- 4.15 -> 100.0
- 3.96 -> 90.0
- 3.91 -> 80.0
- 3.85 -> 70.0
- 3.80 -> 60.0
- 3.75 -> 50.0
- 3.68 -> 40.0
- 3.58 -> 30.0
- 3.49 -> 20.0
- 3.41 -> 10.0
- 3.30 -> 5.0
- 3.27 -> 0.0
- clamp:
min_value: 0
max_value: 100
spi:
clk_pin: GPIO7
mosi_pin: GPIO9
text_sensor:
- platform: homeassistant
id: calendar_event_title
entity_id: ${calendar}
attribute: message
internal: true
- platform: homeassistant
id: calendar_event_time
entity_id: ${calendar}
attribute: start_time
internal: true
time:
- platform: homeassistant
id: ha_time
display:
- platform: waveshare_epaper
id: epaper_display
model: 7.50inv2
cs_pin: GPIO10
dc_pin: GPIO11
reset_pin:
number: GPIO12
inverted: false
busy_pin:
number: GPIO13
inverted: true
update_interval: 30s
rotation: 270
lambda: |-
// Center
int x_center = it.get_width() / 2;
int y_center = it.get_height() / 2;
// Background
it.filled_rectangle(0, 0, it.get_width(), it.get_height(), COLOR_ON);
// Start chart
int img_x = (it.get_width() - 400) / 2;
it.image(img_x, 30, id(star_chart), COLOR_OFF, COLOR_ON);
// Quote
it.printf(x_center, 450, id(font_quote), COLOR_OFF, TextAlign::CENTER, "${quote}");
// Separator
int line_width = it.get_width() * 0.7;
int line_x_start = (it.get_width() - line_width) / 2;
it.filled_rectangle(line_x_start, 490, line_width, 2, COLOR_OFF);
// Event data
it.printf(x_center, 520, id(font_event), COLOR_OFF, TextAlign::CENTER, "${event}");
// Counter
auto now = id(ha_time).utcnow();
int now_year = now.year;
int now_month = now.month;
int now_day = now.day_of_month;
int ref_year = ${event_year};
int ref_month = ${event_month};
int ref_day = ${event_day};
int years = now_year - ref_year;
int months = now_month - ref_month;
int days = now_day - ref_day;
if (days < 0) {
months--;
days += 30;
}
if (months < 0) {
years--;
months += 12;
}
char time_passed[64];
snprintf(time_passed, sizeof(time_passed),
"Hemos pasado %d años, %d meses y %d días juntos",
years, months, days);
it.printf(x_center, 545, id(font_count), COLOR_OFF, TextAlign::CENTER, "%s", time_passed);
// Separator
it.filled_rectangle(line_x_start, 570, line_width, 2, COLOR_OFF);
// Current date
auto now2 = id(ha_time).now();
struct tm timeinfo = now2.to_c_tm();
const char *weekday[] = {"Dom","Lun","Mar","Mie","Jue","Vie","Sab"};
const char *wday = weekday[timeinfo.tm_wday];
const char *months_es[] = {"Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"};
const char *month_name = months_es[timeinfo.tm_mon];
char year_short[4];
snprintf(year_short, sizeof(year_short), "'%02d", timeinfo.tm_year % 100);
it.printf(x_center, 620, id(font_current_date), COLOR_OFF, TextAlign::CENTER,
"%s, %d %s %s", wday, timeinfo.tm_mday, month_name, year_short);
// Calendar
it.printf(x_center, 670, id(font_calendar_event), COLOR_OFF, TextAlign::CENTER,
"%s", id(calendar_event_title).state.c_str());
if (id(calendar_event_time).state.length() >= 16) {
std::string date = id(calendar_event_time).state.substr(8, 2); // Extrae el día (DD)
std::string month = id(calendar_event_time).state.substr(5, 2); // Extrae el mes (MM)
std::string hour = id(calendar_event_time).state.substr(11, 2); // Extrae la hora (HH)
std::string minute = id(calendar_event_time).state.substr(14, 2); // Extrae los minutos (MM)
// Mapea el número del mes a su nombre en español
std::map<std::string, std::string> month_names = {
{"01", "enero"}, {"02", "febrero"}, {"03", "marzo"}, {"04", "abril"},
{"05", "mayo"}, {"06", "junio"}, {"07", "julio"}, {"08", "agosto"},
{"09", "septiembre"}, {"10", "octubre"}, {"11", "noviembre"}, {"12", "diciembre"}
};
std::string month_name = month_names[month]; // Obtiene el nombre del mes
// Imprime el evento y la fecha formateada
it.printf(x_center, 700, id(font_calendar_time), COLOR_OFF, TextAlign::CENTER, "%s de %s, a las %s:%sh", date.c_str(), month_name.c_str(), hour.c_str(), minute.c_str());
}
// Battery level
int bat_x = it.get_width() - 70; // 430px (480-50)
int bat_y = it.get_height() - 40; // 760px (800-40)
it.image(bat_x, bat_y, id(icon_battery), COLOR_OFF);
it.printf(bat_x + 25, bat_y, id(font_battery_level), COLOR_OFF,
"%.0f%%", id(battery_level).state);

