Suena el timbre y no estás en casa. O estás, pero con audífonos puestos en el segundo piso. Un videoportero inteligente resuelve eso por unos doscientos mil pesos, con cuenta en la nube incluida y la molestia de reemplazar una instalación que funciona perfecto.

Hay un camino más corto: dejar el timbre tal como está y solo escucharlo. Un microcontrolador de diez mil pesos detecta el pulso eléctrico que ya viaja por los cables cuando alguien aprieta el botón, y lo publica en Home Assistant como un sensor binario. El citófono sigue sonando igual, nadie en la casa nota diferencia, y tú recibes en el celular una notificación con la foto de quien está en la puerta.

Al terminar este tutorial vas a tener: el timbre convertido en una entidad de Home Assistant, una automatización que te manda la imagen de la cámara al celular en menos de un segundo, y. lo más importante para no quemar nada. el método para medir tu propia instalación antes de conectar el primer cable.

Interior del timbre abierto con el módulo optoacoplador PC817 y la placa ESP32 instalados

Primero medir, después conectar

Esta es la sección que el tutorial original da por sabida y que en Chile es la que más problemas evita, porque acá conviven instalaciones muy distintas: citófonos de edificio, timbres con transformador de campanilla y timbres inalámbricos con receptor enchufado.

Necesitas un multímetro y diez minutos. Abre el interior del timbre. el equipo que está dentro de la casa, no el botón de la reja. y busca la regleta de terminales. Muchos modelos traen salidas rotuladas para conectar una campanilla adicional o un transmisor inalámbrico; esas son exactamente las que sirven.

Paso 1: ¿continua o alterna? Pon el multímetro en tensión continua (V⎓) sobre los dos terminales sospechosos y pídele a alguien que apriete el botón. Si el número salta a un valor positivo estable. típicamente entre 3 y 12 V. tienes señal continua y vas directo. Si el valor baila o marca casi cero, cambia a tensión alterna (V∼) y repite: si ahí aparecen 8, 12 o 24 V, tu timbre trabaja con un transformador de campanilla en alterna.

Paso 2: anota la tensión de reposo. Con nadie apretando el botón, la mayoría de las instalaciones queda en 0 V y sube al timbrar. Algunas hacen justo lo contrario: mantienen la línea alta y la bajan al timbrar. No importa cuál sea tu caso. el firmware tiene un parámetro inverted para dar vuelta la lógica. pero necesitas saberlo de antemano.

Paso 3: verifica que haya alimentación permanente. Si el interior del timbre entrega 5 V constantes en otro par de terminales, puedes alimentar el ESP32 desde ahí y no necesitas enchufe. Mide que se mantengan estables: un ESP32 configurado como router Zigbee no duerme nunca y consume entre 40 y 80 mA de forma continua. Si el ramal de 5 V no aguanta eso, te espera el clásico "en el escritorio funciona, en la pared se reinicia solo". La salida es simple: alimentarlo con un cargador USB aparte.

Si te dio alterna, no conectes nada todavía. La entrada del módulo optoacoplador espera continua. La solución es un puente rectificador de diodos. cuatro 1N4007. seguido de un condensador de 10 µF antes de la entrada del módulo, o bien un solo diodo en antiparalelo con el LED interno para protegerlo del semiciclo negativo. Con el módulo de rango 3,6 a 24 V que usamos acá, el rectificador es el camino limpio.

Por qué un optoacoplador y no un cable directo

El error tentador es conectar la línea del timbre directo a un GPIO. No lo hagas: los pines del ESP32 toleran 3,3 V como máximo y el timbre entrega 5, 12 o 24. Los quemas al primer campanillazo.

El PC817 resuelve esto con física, no con electrónica de precisión. Adentro tiene dos piezas que no se tocan: un LED infrarrojo y un fototransistor, separados por un aislante transparente. La señal del timbre solo enciende el LED. La luz cruza la barrera y satura el fototransistor, que está del otro lado conectado al ESP32. No hay ningún camino conductor entre ambos circuitos: los 24 V del timbre físicamente no pueden llegar al microcontrolador, ni siquiera si algo falla.

Diagrama de conexión: señal del timbre a IN1 del módulo PC817 y la salida OUT1 al GPIO del ESP32

El cableado son cuatro hilos y una decisión:

Desde Hacia
Señal del timbre IN1 del módulo PC817
Masa del timbre G del módulo PC817
OUT1 del módulo PC817 GPIO de entrada del ESP32
GND del módulo PC817 GND del ESP32

La decisión es no conectar el VCC del lado de salida y retirar los jumpers del módulo. Suena contraintuitivo, pero es lo correcto: la salida del PC817 es un colector abierto, o sea que solo sabe tirar el pin hacia masa. El nivel alto lo pone el ESP32 con su resistencia de pull up interna. Así el pin queda en 3,3 V en reposo y cae a 0 V cuando timbran. Como el VCC de salida nunca se conecta, tampoco existe forma de que aparezca una tensión ajena en el GPIO.

Un detalle que ahorra frustración: si compras un módulo de rango 3,6 a 24 V, ya trae la resistencia limitadora del LED montada. Agregarle otra en serie deja pasar tan poca corriente que el optoacoplador conmuta de forma errática. Y si prefieres armarlo discreto con un PC817 suelto en DIP-4, la resistencia sí la pones tú: 1 kΩ para señales de 5 V, 4,7 kΩ para 12 V.

Cada canal del módulo tiene su propio LED indicador. Eso te deja probar la instalación antes de escribir una línea de configuración: aprieta el botón del timbre y mira si el LED del módulo se enciende. Si no lo hace, el problema está en los cables del timbre y no en Home Assistant.

La placa: qué se consigue en Chile

El proyecto original usa un Waveshare ESP32-H2-Zero. El H2 es el raro de la familia: no tiene WiFi, solo radio 802.15.4. Zigbee y Thread. más Bluetooth LE. Para un sensor que va enchufado permanentemente eso es ideal, porque entra a la malla Zigbee como router y de paso repite la señal para el resto de tus dispositivos.

Ese modelo no se vende en Chile, pero hay un reemplazo honesto: el Seeed Studio XIAO ESP32-C6. El C6 lleva la misma radio 802.15.4. así que también hace Zigbee y Thread. y además trae WiFi 6. Eso abre dos caminos, y conviene elegir antes de flashear:

Ruta Zigbee (C6 o H2) Ruta WiFi + API de ESPHome
Necesitas coordinador Zigbee No
Actualizaciones por aire (OTA) No, siempre por cable USB
Repite la malla Zigbee No
Sobrevive a la caída del WiFi No
Consumo típico 40 a 80 mA 80 a 120 mA

La limitación de la ruta Zigbee es la que más duele y el tutorial original la advierte con razón: sin WiFi no hay API de ESPHome ni OTA. Cada cambio de configuración significa bajar el aparato de la pared, conectarlo por USB, reflashear y volver a emparejarlo en Zigbee. Por eso la regla de oro es dejar la configuración terminada y probada en el escritorio antes de que el ESP32 entre a la caja del timbre.

La documentación de ESPHome recomienda además el H2 por sobre el C6 para uso exclusivo de Zigbee, porque hay reportes de placas C6 que solo enganchan estando cerca del coordinador. Si ya tienes una malla Zigbee andando y el timbre queda lejos del coordinador, considera esa advertencia. Si no tienes coordinador Zigbee, la ruta WiFi con un ESP32 WROOM común es más simple, más barata y conserva el OTA.

La configuración en ESPHome

Zigbee viene integrado en ESPHome desde comienzos de 2026. Muchos tutoriales que encuentras en internet todavía cargan un external_components para esto: está obsoleto, no lo necesitas.

Esta es la configuración del proyecto original, para el ESP32-H2:

YAML
esphome:
  name: doorbell-sensor
  friendly_name: doorbell-sensor

esp32:
  variant: esp32h2
  flash_size: 4MB
  framework:
    type: esp-idf

logger:
  level: INFO

zigbee:
  id: zb
  router: true              # Router en vez de End Device = repetidor
  power_source: DC_SOURCE   # alimentado de la red, no duerme
  on_join:
    then:
      - logger.log: "Zigbee-Netz beigetreten"

sensor:
  - platform: uptime
    name: "Uptime"
    update_interval: 60s

binary_sensor:
  - platform: gpio
    id: klingel_gpio
    name: "Klingel"
    report: force        # Obligatorio con ZHA, si no el intervalo es de 30 s
    pin:
      number: GPIO12
      inverted: true     # El optoacoplador tira el pin a GND
      mode:
        input: true
        pullup: true
    filters:
      - delayed_on: 30ms
      - delayed_off: 500ms

Y esta es la adaptación al XIAO ESP32-C6 que se consigue acá. Cambian dos cosas: la variante del chip y el pin, porque el XIAO expone D0 como GPIO0:

YAML
esphome:
  name: sensor-timbre
  friendly_name: Sensor Timbre

esp32:
  variant: esp32c6
  flash_size: 4MB
  framework:
    type: esp-idf

logger:
  level: INFO

zigbee:
  id: zb
  router: true
  power_source: DC_SOURCE

sensor:
  - platform: uptime
    name: "Uptime"
    update_interval: 60s

binary_sensor:
  - platform: gpio
    id: timbre_gpio
    name: "Timbre"
    report: force
    pin:
      number: GPIO0
      inverted: true
      mode:
        input: true
        pullup: true
    filters:
      - delayed_on: 30ms
      - delayed_off: 500ms

Cuatro líneas concentran todo el criterio del diseño:

router: true convierte el nodo en router Zigbee en lugar de dispositivo final. Es lo que lo transforma en repetidor de la malla.

power_source: DC_SOURCE le avisa al coordinador que el aparato está enchufado y no va a dormir, así que no intenta gestionarlo como sensor a pila.

report: force es la línea que separa un timbre útil de uno inservible. Sin ella, ZHA limita el reporte a un intervalo de 30 segundos: la visita ya se habría ido antes de que llegue la notificación.

Los filtros delayed_on: 30ms y delayed_off: 500ms son antirrebote. El de 30 ms descarta los microcortes del contacto mecánico del botón, que de otro modo generarían tres o cuatro eventos por timbrazo. El de 500 ms evita que un timbrazo largo se fragmente en varios eventos separados.

El sensor uptime cumple una función que en la ruta Zigbee no es cosmética: es el único signo vital del aparato. Sin API de ESPHome no hay forma de preguntarle si sigue vivo, así que ese contador subiendo en Home Assistant es lo que te confirma que no se colgó.

Y algo igual de importante es lo que no aparece: no hay wifi:, no hay api:, no hay ota:. Es a propósito. Como efecto colateral el aparato figura permanentemente como "offline" en el panel de ESPHome. Es normal, no es una falla.

Si prefieres la ruta WiFi con MQTT

Sin coordinador Zigbee, la misma entrada funciona con cualquier ESP32 con WiFi publicando por MQTT, y conservas el OTA:

YAML
esphome:
  name: sensor-timbre
  friendly_name: Sensor Timbre

esp32:
  board: esp32dev
  framework:
    type: arduino

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

logger:
ota:
  - platform: esphome
    password: !secret ota_password

mqtt:
  broker: 192.168.1.10
  username: !secret mqtt_user
  password: !secret mqtt_password

binary_sensor:
  - platform: gpio
    id: timbre_gpio
    name: "Timbre"
    device_class: sound
    pin:
      number: GPIO12
      inverted: true
      mode:
        input: true
        pullup: true
    filters:
      - delayed_on: 30ms
      - delayed_off: 500ms

Con el descubrimiento automático de MQTT activado en Home Assistant, la entidad aparece sola. La contrapartida es la esperable: si se cae el WiFi o el broker, el timbre deja de reportar.

Crear el dispositivo y flashearlo

El complemento de ESPHome dentro de Home Assistant hace todo el trabajo. Abajo a la derecha, Crear dispositivo, y en el diálogo siguiente Crear proyecto nuevo:

Panel de ESPHome en Home Assistant con el diálogo para crear un dispositivo nuevo

En la selección de placa, filtra por el chip que tengas. Ni el Waveshare ESP32-H2-Zero ni el XIAO ESP32-C6 aparecen por nombre en la lista, pero ambos son compatibles en pines con los DevKit de referencia, así que elegir la tarjeta genérica del chip correspondiente funciona:

Selección de placa en ESPHome filtrando por el chip y eligiendo la tarjeta DevKit correspondiente

Después le pones nombre. De ahí ESPHome deriva solo el nombre de host:

Asignación del nombre del dispositivo en el asistente de ESPHome

El asistente genera una configuración por defecto con credenciales de WiFi. Bótala completa: haz clic en Editar y reemplaza todo el contenido por el YAML de más arriba.

Editor YAML de ESPHome con la configuración Zigbee cargada

Guarda e Instala. Si elegiste la ruta Zigbee, la única opción posible es por cable: Conectado a este computador. El diálogo promete que las actualizaciones futuras se pueden hacer por red, pero eso vale para placas con WiFi; acá seguirá siendo por USB siempre.

Diálogo de instalación de ESPHome con la opción de instalar por cable USB seleccionada

El navegador pregunta qué puerto serie usar. El chip se identifica como USB JTAG/serial debug unit: esa es la entrada correcta. Dos advertencias que cuestan tiempo: esto funciona únicamente en Chrome o Edge, porque Firefox no implementa Web Serial; y el cable USB tiene que ser de datos. Muchos cables baratos llevan solo los hilos de alimentación, y con uno de esos la placa ni siquiera aparece en la lista.

Selección del puerto serie en el navegador mostrando la entrada USB JTAG/serial debug unit

La primera compilación demora bastante más de lo habitual porque ESPHome descarga la cadena de herramientas completa de ESP IDF para RISC-V. No es que se haya colgado. Cuando termina de grabar, la ventana de registro muestra el primer arranque:

Registro de arranque por Web Serial mostrando setup() finished successfully y la revisión del chip

Tres líneas confirman que quedó bien: setup() finished successfully, la versión de ESPHome, y sobre todo la línea del chip detectado. Si ahí aparece un chip distinto al que configuraste, el variant del YAML no corresponde a tu hardware. Más arriba, en la tabla de particiones, buscar zb_fct confirma que el área de memoria Zigbee quedó creada.

Deja el cable USB conectado. En la ruta Zigbee, esa ventana de registro es tu única visibilidad hacia adentro del aparato: no hay registros por red.

Emparejar y probar

El emparejamiento es el mismo de cualquier dispositivo Zigbee. Con ZHA: Configuración → Zigbee → Agregar dispositivo. Le pones nombre y pieza, y el ESP32 aparece con sus dos entidades: la duración de encendido y el timbre.

Sensor de timbre conectado y visible en Home Assistant

Unos cuantos timbrazos de prueba muestran el sensor saltando entre apagado y encendido:

Entidad del timbre cambiando de estado en Home Assistant al presionar el botón

Si el sensor queda permanentemente en on, casi siempre es un jumper del módulo optoacoplador que quedó puesto y está tirando la salida a masa. Sácalo. Si nunca cambia, vuelve al LED indicador del módulo: si tampoco enciende, el problema está aguas arriba, en los cables del timbre.

La automatización: foto en el celular al instante

Con el timbre convertido en sensor binario, lo interesante empieza. La automatización manda una notificación a la aplicación Home Assistant Companion con la foto de la cámara que apunta a la puerta.

El orden obvio. primero la foto, después el aviso. tiene una trampa: el mensaje queda esperando a la cámara. Con una cámara 4K, capturar un cuadro completo puede tomar varios segundos, que son justamente los que necesitas para llegar a la puerta.

Por eso esta automatización invierte el orden. Primero sale un aviso sin adjunto, que llega al celular casi instantáneo. Después, con calma, se toma la foto, y una segunda notificación con el mismo tag reemplaza a la primera. No recibes dos mensajes: recibes uno que se completa con su imagen.

YAML
alias: Timbre - Stream de la cámara al celular
description: Aviso inmediato al timbrar, la foto llega después
triggers:
  - trigger: state
    entity_id:
      - binary_sensor.klingel
    to: "on"
actions:
  # 1. Sale de inmediato, sin tocar la cámara, sin adjunto
  - action: notify.mobile_app_dein_smartphone
    data:
      title: "🔔 Están tocando el timbre"
      message: "La foto viene en camino …"
      data:
        tag: klingel
        push:
          interruption-level: time-sensitive

  # 2. Foto desde la entidad de stream local
  - action: camera.snapshot
    target:
      entity_id: camera.hausture_rtsp
    data:
      filename: /config/www/klingel_schnappschuss.jpg

  # 3. Se sobrescribe el mismo aviso, ahora con imagen
  - action: notify.mobile_app_dein_smartphone
    data:
      title: "🔔 Están tocando el timbre"
      message: "Toca para ver el stream en vivo"
      data:
        tag: klingel
        attachment:
          url: /local/klingel_schnappschuss.jpg
        entity_id: camera.hausture_rtsp
        push:
          sound: none
          interruption-level: time-sensitive
mode: single

Reemplaza notify.mobile_app_dein_smartphone por tu propio servicio. lo encuentras en Herramientas para desarrolladores → Acciones buscando "notify". y camera.hausture_rtsp por tu cámara. El nombre del sensor, binary_sensor.klingel, es el que ESPHome generó a partir del YAML; si usaste la versión adaptada, será binary_sensor.timbre.

Los detalles que hacen la diferencia:

La carpeta /config/www/ se sirve en Home Assistant bajo la ruta /local/. Si no existe, créala y reinicia Home Assistant: sin reinicio el archivo se escribe pero no se entrega, y la notificación llega sin imagen. Para verificar, abre http://<IP-de-tu-HA>:8123/local/klingel_schnappschuss.jpg desde el navegador estando en la misma red. Si da 404, falta la carpeta o el reinicio. Si muestra la foto, el problema está en la notificación y no en la captura.

Usa attachment: url: y no image:. La aplicación de iOS solo interpreta attachment.url y entity_id; no tiene analizador para image. Además la ruta debe ser relativa, empezando con barra: la aplicación adjunta sus credenciales solo cuando la URL parte así.

En iPhone no definas url ni clickAction. La aplicación toma el primer valor que encuentre entre url, uri y clickAction y lo abre; solo si ninguno existe cae en entity_id y abre la cámara. En Android es al revés: hay que agregar clickAction: entityId:camera.hausture_rtsp.

interruption-level: time-sensitive es específico de iOS y hace que el aviso atraviese los modos de concentración y "No molestar". Sin esa línea, iOS lo retiene mientras el teléfono duerme y la automatización parece lenta cuando en realidad funcionó al instante. En Android el equivalente es priority: high junto con ttl: 0.

Para la foto, usa la entidad de stream local (RTSP u ONVIF), no la entidad en la nube de la integración del fabricante. Las entidades en la nube muy seguido devuelven datos vacíos, y en iPhone eso se manifiesta como el mensaje "input data was nil or zero length" dentro de la notificación desplegada. Si tu cámara ofrece substream, úsalo: Home Assistant tiene que esperar un fotograma clave completo, y en 4K eso demora mucho más que en 640×480. Como miniatura de notificación no vas a notar la diferencia.

Una advertencia de privacidad: los archivos de la carpeta www no están protegidos por la sesión de Home Assistant. Quien conozca la URL ve la foto de tu puerta. Si eso te incomoda, escribe la captura en /media/klingel_schnappschuss.jpg y refiérela como /media/local/klingel_schnappschuss.jpg; esa ruta sí exige autenticación, que la aplicación Companion envía sola.

Un último tropiezo garantizado: si armas la automatización con el editor visual, vas a terminar usando la acción notify.send_message, que solo acepta message y title. Apenas agregues imagen, entidad de cámara o acción al tocar, Home Assistant se niega a guardar con un error tipo extra keys not allowed @ data['image']. Todo lo demás que sabe hacer la aplicación existe únicamente en el servicio clásico notify.mobile_app_…, y los parámetros extra van un nivel más abajo, dentro de un segundo data:, tal como está en el ejemplo.

Variantes y mejoras

Sin soldador: la ruta del relé. Si prefieres no armar nada, un módulo de entrada seco conectado en paralelo a la campanilla hace el mismo trabajo. Cualquier dispositivo con entrada libre de potencial sirve, y varios funcionan tanto con 220 V como con 12 o 24 V continuos. El principio es idéntico: el módulo detecta el pulso y lo reporta como sensor binario mientras el timbre sigue sonando normal.

Ventana emergente en un tablet de pared. Si tienes un tablet con el panel de Home Assistant colgado en el pasillo, la extensión browser_mod de HACS agrega una ventana con la cámara en vivo al timbrar. Hay una trampa conocida: si el tablet apaga la pantalla para ahorrar energía, su interfaz web queda pausada y la conexión cerrada, así que el aviso se envía a un receptor que no existe. La solución es despertar la pantalla antes de mostrar la ventana, con una llamada al aparato mediante rest_command, y agregar una pausa de medio segundo para que la interfaz alcance a reconectarse.

Registro de visitas. Como el timbre ya es una entidad, agrega un counter en Home Assistant que se incremente en cada evento y una plantilla que guarde la fecha del último timbrazo. Con eso el panel te muestra "3 visitas hoy, la última a las 15:42" sin instalar nada adicional.

Timbre distribuido por la casa. Con el evento en Home Assistant, cualquier parlante inteligente o Chromecast puede reproducir un sonido al timbrar. Es la forma barata de tener campanilla en el segundo piso sin tender un solo cable.

Personalización para Chile

Todo lo necesario está disponible en el catálogo de MechatronicStore:

  • Seeed Studio XIAO ESP32-C6 (SKU GS3-5). $10.190 CLP
  • Módulo Optoacoplador de Aislamiento 2 Canales PC817 (SKU X2-15V2). $2.490 CLP
  • Módulo Coordinador Red ZigBee CC2531 Domótica Home Assistant (SKU GL2-9). $9.690 CLP
  • Cable USB Tipo C a USB Tipo A 1mt (SKU B-101). $2.190 CLP
  • Cables macho hembra 40 piezas 20cm (SKU C-412). $1.990 CLP
  • Breadboard / Protoboard 830 puntos MB102 (SKU C-302). $3.790 CLP
  • Conector Terminal 2 Pines 5mm screw Con Tornillo Kf301-2 (SKU GC2-1). $290 CLP
  • Multimetro Multi Tester 830 series (SKU E-304). $2.990 CLP

Las equivalencias que importan:

El tutorial original usa un Waveshare ESP32-H2-Zero, que no llega a Chile. El XIAO ESP32-C6 del catálogo lleva la misma radio 802.15.4 y por lo tanto hace Zigbee igual; suma WiFi 6, que el H2 no tiene. La diferencia real es la advertencia de la documentación de ESPHome sobre el alcance de algunas placas C6 respecto del coordinador. Si tu timbre queda a más de dos piezas de distancia, ubica un repetidor Zigbee intermedio.

Si no tienes malla Zigbee armada, un ESP32 WROOM común (SKU X2-10V2, $7.990 CLP) con la configuración WiFi + MQTT de más arriba cumple exactamente la misma función, cuesta menos y conserva las actualizaciones por aire. Es la ruta que recomendamos para empezar.

El coordinador CC2531 solo hace falta si vas por Zigbee y todavía no tienes uno. Si ya usas ZHA o Zigbee2MQTT, sáltalo.

Dos cosas que este proyecto necesita y que no se venden en tienda: un servidor con Home Assistant ya instalado. una Raspberry Pi, un mini PC o un NUC sirven. y una cámara IP con salida RTSP u ONVIF apuntando a la puerta. Sin la cámara el sensor igual funciona: recibes la notificación, solo que sin foto.

Presupuesto aproximado de la ruta Zigbee completa: $33.620 CLP. Por la ruta WiFi, sin coordinador: $19.240 CLP.

Recursos

Versión chilena inspirada en el tutorial de raspberry.tips, adaptada al ESP32-C6 y con la sección de medición previa del timbre agregada. Componentes en stock local en MechatronicStore.