Los dispositivos ESP32 se han convertido en algo indispensable para el DIY, este pequeño ESP32-C3 con pantalla de 0.42″ es un ejemplo de lo que podemos hacer con ellos. Aunque la pantalla es pequeña, quiero mostraros el vídeo para que veais lo que permite hacer, así como enseñaros como podéis hacer uso de Gemini para poder llegar a programar en unos minutos una interfaz bastante completa. ¿te apuntas? Pues mira el vídeo
Tutorial para programar el ESP32-C3 con ESPHome y Gemini
Recursos usados:
Compra del ESP32-C3 aquí
Cupones Aliexpress activos:
✂️ IFPTK8ZE (-2$ para más de 15$)
✂️ IFPD66S3 (-4$ para más de 29$)
✂️ IFPGRIZO (-7$ para más de 49$)
✂️ IFP9BF4K (-10$ para más de 79$)
✂️ IFPOYCQH (-15$ para más de 109$)
✂️ IFPWDRAM (-20$ para más de 159$)
✂️ IFPF3XLF (-30$ para más de 249$)
✂️ IFPXFSGJ (-45$ para más de 369$)
✂️ IFP87CSO (-60$ para más de 499$)
✂️ IFPNW3PU (-3$ para más de 29$)
✂️ IFPSGNN2 (-6$ para más de 59$)
✂️ IFPF71HL (-9$ para más de 89$)
✂️ IFPO2ENC (-16$ para más de 149$)
✂️ IFPEWAKD (-23$ para más de 199$)
✂️ IFPWLEGR (-30$ para más de 269$)
✂️ IFPDVEWH (-40$ para más de 369$)
✂️ IFPL7BLY (-50$ para más de 469$)
✂️ IFPLVL6G (-60$ para más de 599$)
✂️ IFPHDV9P (-70$ para más de 699$)
Encuentra las ofertas:
🔥CUPONES LIMITADOS,¡ATENTO QUE VOLARÁN!🔥Por si queréis más ofertas podéis verlas aquí.
* Si os salen agotados, podéis avisarme en los comentarios e intento buscar nuevos.
Código ESPHome inicial:
esphome:
name: abrobot-dec
friendly_name: abrobot-dec
esp32:
board: esp32-c3-devkitm-1
framework:
type: esp-idf
# Habilitar log (necesario para ver si conecta bien por USB)
logger:
# Habilitar API para Home Assistant
api:
# Habilitar actualizaciones inalámbricas (IMPORTANTE)
ota:
- platform: esphome
# Configuración WiFi usando tus secretos
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Red de rescate por si falla tu WiFi
ap:
ssid: "Abrobot-Dec Fallback"
password: "password_rescate" # Debe tener al menos 8 caracteres
captive_portal: Prompts usados:
esphome:
name: abrobot-dec
friendly_name: abrobot-dec
esp32:
board: esp32-c3-devkitm-1
framework:
type: esp-idf
# Habilitar log (necesario para ver si conecta bien por USB)
logger:
# Habilitar API para Home Assistant
api:
# Habilitar actualizaciones inalámbricas (IMPORTANTE)
ota:
- platform: esphome
# Configuración WiFi usando tus secretos
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Red de rescate por si falla tu WiFi
ap:
ssid: "Abrobot-Dec Fallback"
password: "password_rescate" # Debe tener al menos 8 caracteres
captive_portal:
Necesito que añadas al proyecto de ESPHome que te pego el que muestre la hora en la pantalla que tiene. El modelo de pantalla es "SSD1306 72x40" y debe obtener la hora de Home Assistant que tengo instalado en casa. El i2c en este dispositivo parece ir bien con esta configuración
i2c:
sda: GPIO5
scl: GPIO6
scan: true
id: bus_i2c
frequency: 400kHz Ahora necesito que añadas el hecho de que el dispositivo sea un proxy bluetooth para hasta 3 dispositivos
Vamos a mejorar el dispositivo haciendo que tenga pantallas que cambian cada 10 segundos, déjalo puesto para poder añadir más pantallas en el futuro, la primera pantalla será la hora, la siguiente será la meteorología dibujando la información de la siguiente manera: En la parte superior centrado pondrá el nombre de la ciudad en pequeño (sensor de Home Assistant sensor.aemet_town_name), en los 2/3 de pantalla que quedan debe mostrar en la mitad izquierda la condición meteorologica con un icono usando la fuente material design (el sensor será sensor.aemet_daily_forecast_condition y los posibles valores que usa el sensor son: sunny, clear-night, cloudy, partlycloudy, rainy, fog), en la parte derecha mostrará la temperatura actual en un tamaño grande (sensor sensor.aemet_temperature) y, al lado en pequeño pondrá “ºC”
Vamos a añadir otra pantalla, ahora toca la previsión meteorologica de temperatura, al igual que antes, en la parte superior centrada pondrá el nombre de la ciudad, debajo, también en pequeño pondrá “Previsión” (recuerda importar el caracter con la tilde) y para la previsión pondrá la temperatura máxima que es el sensor sensor.aemet_daily_forecast_temperature dibujará una “/” para separar y luego pondrá la mínima del sensor.aemet_daily_forecast_temperature_low
Vamos a añadir otra pantalla: ésta se dividirá en 2, a la izquierda ocupando 1/3 de la pantalla pondrás el icono de la cara de un gato (usando la fuente) y en los 2/3 de la derecha pondrás en la parte superior el valor de sensor.pb01009g25030010664_cat_litter_level y le pondrás después “%” (recuerda importar este caracter si es necesario) ya que es un valor porcentual. En la parte inferior pondrás un texto en función del valor del sensor binario binary_sensor.pb01009g25030010664_garbage_can_full, si está OFF escribirás “OK”, si está ON escribirás “LIMPIAR”
Recordad que tendréis que adaptarlo a vuestras necesidades y a los nombres de las entidades de vuestro Home Assistant para que funcione.
Código final (por si queréis adaptarlo nada más):
esphome:
name: abrobot-dec
friendly_name: abrobot-dec
esp32:
board: esp32-c3-devkitm-1
framework:
type: esp-idf
logger:
api:
ota:
- platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
manual_ip:
# La IP que quieres que tenga SIEMPRE (cámbiala por una libre)
static_ip: 192.168.1.132
# La IP de tu router
gateway: 192.168.1.1
# La máscara de subred (normalmente no cambia)
subnet: 255.255.255.0
ap:
ssid: "Abrobot-Dec Fallback"
password: "password_rescate"
captive_portal:
i2c:
sda: GPIO5
scl: GPIO6
scan: true
id: bus_i2c
frequency: 400kHz
# --- SENSORES DE HOME ASSISTANT ---
sensor:
- platform: homeassistant
id: temperatura_actual
entity_id: sensor.aemet_temperature
- platform: homeassistant
id: temp_max
entity_id: sensor.aemet_daily_forecast_temperature
- platform: homeassistant
id: temp_min
entity_id: sensor.aemet_daily_forecast_temperature_low
# NUEVO: Nivel de arena
- platform: homeassistant
id: arenero_nivel
entity_id: sensor.pb01009g25030010664_cat_litter_level
# NUEVO: Sensor binario de basura llena
binary_sensor:
- platform: homeassistant
id: arenero_lleno
entity_id: binary_sensor.pb01009g25030010664_garbage_can_full
text_sensor:
- platform: homeassistant
id: nombre_ciudad
entity_id: sensor.aemet_town_name
- platform: homeassistant
id: condicion_clima
entity_id: sensor.aemet_daily_forecast_condition
time:
- platform: homeassistant
id: hora_ha
# --- FUENTES TIPOGRÁFICAS ---
font:
- file: "gfonts://Roboto"
id: fuente_reloj
size: 20
# Fuente pequeña (con caracteres españoles y símbolos)
- file: "gfonts://Roboto"
id: fuente_small
size: 10
glyphs: " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~º°ñÑáéíóúÁÉÍÓÚ"
- file: "gfonts://Roboto"
id: fuente_temp
size: 18
- file: "gfonts://Material Icons"
id: fuente_iconos
size: 24
glyphs:
- "\U0000E430" # wb_sunny
- "\U0000EA46" # nights_stay
- "\U0000E2BD" # cloud
- "\U0000E42C" # wb_cloudy
- "\U0000E3EA" # grain
- "\U0000E3C7" # dehaze
- "\U0000E000" # error
- "\U0000E91D" # pets (Huella de mascota - Nuevo)
# --- VARIABLES Y TEMPORIZADORES ---
globals:
- id: pantalla_actual
type: int
initial_value: '0'
interval:
- interval: 10s
then:
- lambda: |-
id(pantalla_actual) += 1;
// MODIFICADO: Ahora el límite es 3 (Pantallas: 0, 1, 2, 3)
if (id(pantalla_actual) > 3) {
id(pantalla_actual) = 0;
}
# --- BLUETOOTH PROXY ---
esp32_ble_tracker:
scan_parameters:
interval: 1100ms
window: 1100ms
active: true
bluetooth_proxy:
active: true
# --- PANTALLA ---
display:
- platform: ssd1306_i2c
model: "SSD1306 72x40"
address: 0x3C
i2c_id: bus_i2c
lambda: |-
// -----------------------------
// PANTALLA 0: RELOJ
// -----------------------------
if (id(pantalla_actual) == 0) {
it.printf(36, 20, id(fuente_reloj), TextAlign::CENTER, "%02d:%02d", id(hora_ha).now().hour, id(hora_ha).now().minute);
}
// -----------------------------
// PANTALLA 1: TIEMPO ACTUAL
// -----------------------------
else if (id(pantalla_actual) == 1) {
if (id(nombre_ciudad).has_state()) {
it.printf(36, 0, id(fuente_small), TextAlign::TOP_CENTER, "%s", id(nombre_ciudad).state.c_str());
}
std::string estado = id(condicion_clima).state;
std::string icono = "\U0000E2BD";
if (estado == "sunny") icono = "\U0000E430";
else if (estado == "clear-night") icono = "\U0000EA46";
else if (estado == "cloudy") icono = "\U0000E2BD";
else if (estado == "partlycloudy") icono = "\U0000E42C";
else if (estado == "rainy") icono = "\U0000E3EA";
else if (estado == "fog") icono = "\U0000E3C7";
else icono = "\U0000E000";
it.print(18, 28, id(fuente_iconos), TextAlign::CENTER, icono.c_str());
if (id(temperatura_actual).has_state()) {
it.printf(50, 28, id(fuente_temp), TextAlign::CENTER, "%.0f", id(temperatura_actual).state);
it.print(64, 28, id(fuente_small), TextAlign::CENTER, "ºC");
}
}
// -----------------------------
// PANTALLA 2: PREVISIÓN
// -----------------------------
else if (id(pantalla_actual) == 2) {
if (id(nombre_ciudad).has_state()) {
it.printf(36, 0, id(fuente_small), TextAlign::TOP_CENTER, "%s", id(nombre_ciudad).state.c_str());
}
it.print(36, 11, id(fuente_small), TextAlign::TOP_CENTER, "Previsión");
if (id(temp_max).has_state() && id(temp_min).has_state()) {
it.printf(36, 28, id(fuente_temp), TextAlign::CENTER, "%.0f/%.0f", id(temp_max).state, id(temp_min).state);
}
}
// -----------------------------
// PANTALLA 3: ARENERO GATO (NUEVA)
// -----------------------------
else if (id(pantalla_actual) == 3) {
// 1. Icono Izquierda (1/3 pantalla -> Centrado en x=12)
// Usamos el icono 'pets' (huella)
it.print(12, 20, id(fuente_iconos), TextAlign::CENTER, "\U0000E91D");
// 2. Nivel % (Derecha Arriba -> Centrado en x=48)
if (id(arenero_nivel).has_state()) {
// Nota: Para escribir '%' dentro de un printf se usa '%%'
it.printf(48, 10, id(fuente_temp), TextAlign::CENTER, "%.0f%%", id(arenero_nivel).state);
}
// 3. Estado (Derecha Abajo -> Centrado en x=48)
// Usamos binary_sensor: ON = Lleno/Limpiar, OFF = Vacío/OK
if (id(arenero_lleno).has_state()) {
if (id(arenero_lleno).state) {
// Estado ON -> Contenedor lleno
it.print(48, 30, id(fuente_small), TextAlign::CENTER, "LIMPIAR");
} else {
// Estado OFF -> Todo bien
it.print(48, 30, id(fuente_small), TextAlign::CENTER, "OK");
}
}
}