¿Cuántas veces descartaste el aviso de "lavado terminado" y la ropa se quedó húmeda en la máquina hasta la noche? ¿O te preguntaste si ya te tomaste la pastilla del día? El problema no es que falte el aviso, es que descartarlo cuesta lo mismo que cumplirlo. Un botón de "listo" en la notificación se aprieta desde el sillón.
La solución de este tutorial es simple: un sticker NFC pegado en la máquina o en el pastillero. Home Assistant te recuerda la tarea cada 30 minutos y solo la da por cumplida cuando escaneas el sticker con el celular, y para escanearlo tienes que estar frente a la máquina. Vas a aprender a escribir los stickers (con la app o con un lector PN532 en una Raspberry Pi), a armar el recordatorio con un interruptor auxiliar y una automatización, y a saber hasta dónde llega el sistema.
La idea en tres piezas
- Un interruptor auxiliar (
input_boolean) recuerda si la tarea está pendiente. Lo enciende el aviso de fin de ciclo de la lavadora o, en el caso de la pastilla, la hora. - Una automatización escucha tres disparadores: el interruptor que se enciende, un reloj cada 30 minutos y el escaneo del sticker. Con los dos primeros manda el recordatorio si la tarea sigue pendiente; con el escaneo apaga el interruptor y confirma.
- El sticker NFC solo guarda una dirección:
https://www.home-assistant.io/tag/<identificador>. El celular reconoce que esa dirección es de la app de Home Assistant, y la app le avisa a tu servidor qué sticker se escaneó.
Como el estado vive en el interruptor y no en la notificación, puedes descartar los recordatorios sin miedo: a la media hora llega otro. Y el interruptor conserva su estado aunque reinicies Home Assistant.
Qué necesitas
- Stickers NFC NTAG213 (redondos de 25 mm son lo más común). Tienen que ser NTAG21x: los MIFARE Classic de 1K no los lee un iPhone y no sirven para esto.
- Un celular con la app de Home Assistant: iPhone XS/XR o más nuevo, o un Android con NFC. Cada persona que vaya a escanear necesita la app en su teléfono.
- Para el recordatorio de la lavadora: un enchufe inteligente con medición de consumo (el original usa un Shelly) y una automatización que detecte el fin de ciclo por la potencia.
- Opcional: una Raspberry Pi con un lector PN532, si quieres escribir muchos stickers seguidos, verificarlos o bloquearlos contra reescritura.
No pegues el sticker directo sobre metal: la plancha de acero inoxidable de una lavavajillas absorbe el campo y el celular no lo lee. Para eso existen stickers "on metal", con una capa de ferrita atrás. Sobre plástico, madera o vidrio funcionan los normales.
Escribir los stickers
Con la app de Home Assistant (lo más fácil)
En la app, entra a Ajustes → Conectividad → Tags y toca Agregar tag. Ponle un nombre (por ejemplo, "Lavadora") y, en la opción de identificador personalizado, escribe uno legible como lavadora. Si lo dejas vacío, la app inventa uno al azar. Toca Crear y escribir y acerca la parte de arriba del celular al sticker hasta que confirme.

Usa identificadores en minúsculas, sin tildes, eñes ni espacios. Es lo que después va en tag_id dentro de la automatización.
Con un PN532 en la Raspberry Pi
El lector vale la pena si vas a preparar varios stickers, si quieres leerlos de vuelta para verificar o si los quieres bloquear. El PN532 se conecta a la Raspberry Pi por I²C (SDA, SCL, 3,3 V y GND, con los interruptores del módulo en modo I²C) y usa la librería adafruit_pn532 dentro de un entorno virtual en ~/nfc-env.

Crea el archivo:
nano nfc_ha_tag.py
y pega este script. Construye el mensaje NDEF a mano, lo escribe página por página (cada página del NTAG213 son 4 bytes, y la memoria del usuario parte en la página 4), lo vuelve a leer para compararlo y, solo si se lo pides, lo bloquea:
import signal
import sys
import board
import busio
from adafruit_pn532.i2c import PN532_I2C
# usage: python nfc_ha_tag.py <tag-id> [--lock]
args = [a for a in sys.argv[1:] if a != "--lock"]
LOCK = "--lock" in sys.argv[1:]
# Home Assistant tag URL; the part after /tag/ becomes the tag ID in Home Assistant
TAG_ID = args[0] if args else "spuelmaschine"
URL = f"www.home-assistant.io/tag/{TAG_ID}"
running = True
def stop(signum, frame):
# finish the current command first, so the PN532 is not left mid-command
global running
running = False
signal.signal(signal.SIGINT, stop)
def ndef_url_message(url):
# NDEF URI record, prefix 0x04 = "https://"
payload = bytes([0x04]) + url.encode()
record = bytes([0xD1, 0x01, len(payload), 0x55]) + payload
# TLV: 0x03 = NDEF message, then length, record, 0xFE = terminator
data = bytes([0x03, len(record)]) + record + bytes([0xFE])
# pad to full 4-byte pages
return data + bytes((-len(data)) % 4)
def write_page(page, block):
# a weak field can drop single writes; select the sticker again and retry
for _ in range(3):
if pn532.ntag2xx_write_block(page, block):
return True
pn532.read_passive_target(timeout=0.5)
return False
def lock_ntag213():
# NTAG213 datasheet 8.5.2-8.5.4: all lock bits are OR'ed in and can never be cleared
# dynamic lock bytes (page 0x28): pages 16-39 read-only, RFUI bits stay 0
if not write_page(0x28, bytes([0xFF, 0x0F, 0x3F, 0x00])):
return False
# capability container byte 3 = 0x0F: NFC Forum "read-only" flag for phones
if not write_page(3, bytes([0x00, 0x00, 0x00, 0x0F])):
return False
# static lock bytes (page 2, bytes 2-3): pages 3-15 read-only; bytes 0-1 are ignored
return write_page(2, bytes([0x00, 0x00, 0xFF, 0xFF]))
def lock_verified():
# the PN532 reports some refused writes as OK, so read the lock bits back
static = pn532.ntag2xx_read_block(2)
cc = pn532.ntag2xx_read_block(3)
dynamic = pn532.ntag2xx_read_block(0x28)
if None in (static, cc, dynamic):
return False
return static[2:4] == b"\xff\xff" and cc[3] == 0x0F and dynamic[0:3] == b"\xff\x0f\x3f"
if LOCK:
print("--lock makes the sticker read-only FOREVER. It can never be rewritten.")
if input("Type LOCK to continue: ").strip() != "LOCK":
sys.exit("Not locking. Nothing was written.")
i2c = busio.I2C(board.SCL, board.SDA)
pn532 = PN532_I2C(i2c, debug=False)
pn532.SAM_configuration()
data = ndef_url_message(URL)
print(f"Writing https://{URL} ({len(data)} bytes, {len(data) // 4} pages)")
print("Hold the sticker on the reader ... (stop with Ctrl+C)")
uid = None
while running and uid is None:
uid = pn532.read_passive_target(timeout=0.5)
if uid is None:
sys.exit("Stopped.")
print(f"Sticker found, UID {uid.hex(':')}")
# page 3 = capability container; E1 = NDEF formatted, 0x12 = 144 bytes (NTAG213)
cc = pn532.ntag2xx_read_block(3)
print(f"Capability container: {cc.hex(' ')}")
if cc[0] != 0xE1:
sys.exit("Not NDEF formatted - is this an NTAG sticker?")
if cc[3] != 0x00:
sys.exit("Sticker is read-only (locked) and cannot be rewritten.")
if len(data) > cc[2] * 8:
sys.exit("URL too long for this sticker.")
if LOCK and cc[2] != 0x12:
sys.exit("--lock only supports NTAG213 stickers. Nothing was written.")
# user memory starts at page 4
for i in range(0, len(data), 4):
if not write_page(4 + i // 4, data[i:i + 4]):
sys.exit(f"Writing page {4 + i // 4} failed - keep the sticker still and retry.")
check = b"".join(pn532.ntag2xx_read_block(4 + i // 4) for i in range(0, len(data), 4))
if check != data:
sys.exit("Read-back differs - write again.")
print(f"Done. Verified: https://{URL}")
if LOCK:
if lock_ntag213() and lock_verified():
print(f"Locked. Capability container now: {pn532.ntag2xx_read_block(3).hex(' ')}")
else:
sys.exit("Locking failed or incomplete - lock bits could not be verified.")
Ejecútalo con el identificador que quieras:
source ~/nfc-env/bin/activate
python nfc_ha_tag.py spuelmaschine
Con un sticker nuevo, la salida se ve así:
Writing https://www.home-assistant.io/tag/spuelmaschine (48 bytes, 12 pages)
Hold the sticker on the reader ... (stop with Ctrl+C)
Sticker found, UID 53:ac:45:ac:43:00:01
Capability container: e1 10 12 00
Done. Verified: https://www.home-assistant.io/tag/spuelmaschine
Cómo leer esa salida. El e1 al inicio del contenedor de capacidades significa que el sticker está formateado para NDEF, y el 12 (hexadecimal) indica 144 bytes de memoria, la del NTAG213. La dirección ocupa 48, así que sobra espacio. El script se protege de tres errores: un sticker que no es NTAG, uno bloqueado y una dirección que no cabe.
Dos cuidados con el PN532. Deja el sticker plano y centrado sobre la antena: torcido o con distancia, el chip rechaza páginas sueltas (el script reintenta cada una hasta tres veces). Y una vez que aparece "Sticker found", no cortes el script con Ctrl+C: un PN532 interrumpido a mitad de un comando puede dejar trabado el bus I²C hasta que lo desconectes.
Bloquear un sticker (es para siempre)
Si el sticker va en un lugar donde cualquiera llega, como la puerta de la casa o la bodega del patio, alguien podría reescribirlo con una app NFC cualquiera. El script lo bloquea con --lock y pide que escribas LOCK para confirmar:
python nfc_ha_tag.py tablette --lock
Escribe los bits de bloqueo que define la hoja de datos del NTAG213 y cambia el último byte del contenedor de capacidades a 0f, la marca que le dice a los celulares que es de solo lectura. No hay vuelta atrás: esos bits solo se pueden encender, nunca apagar. Para stickers dentro de la casa no hace falta.
El primer escaneo
El iPhone lee NFC en segundo plano, pero solo con la pantalla encendida. Acerca la parte de arriba del teléfono al sticker y aparece una notificación de Home Assistant; tócala para que se abra la app.

La primera vez, la app pregunta si quieres permitir ese tag y muestra su identificador. Elige Permitir siempre y no vuelve a preguntar.

En Home Assistant, el sticker aparece en Ajustes → Tags con la hora del último escaneo, y además se crea una entidad (tag.lavadora, en nuestro ejemplo) cuyo estado es ese momento. Si un escaneo no llega, escanea de nuevo: las automatizaciones de abajo están hechas para que un escaneo doble no rompa nada.

En Android, según la documentación de la app, el escaneo se dispara directo, sin tocar la notificación. En iPhone, evitar ese toque exige una automatización de Atajos con disparador NFC en cada teléfono, y las automatizaciones tendrían que usar otro disparador; no compensa.
Receta 1: sacar la ropa de la lavadora (o vaciar la lavavajillas)
Esta receta se apoya en una automatización que ya detecta el fin de ciclo midiendo el consumo de la máquina. El original trabaja con la lavavajillas; para la lavadora es igual, cambiando nombres e identificadores. Los fragmentos de código de abajo vienen tal cual del original, con los nombres en alemán (spulmaschine es lavavajillas, ausraumen es vaciar): cámbialos por los tuyos de forma consistente.
Paso 1. El interruptor auxiliar. En Ajustes → Dispositivos y servicios → Ayudantes → Crear ayudante, crea un interruptor (input boolean) llamado, por ejemplo, "Lavadora por vaciar". Home Assistant le pone como identificador input_boolean.lavadora_por_vaciar; si usas tildes o eñes en el nombre, las quita.
Paso 2. Encenderlo al terminar el ciclo. En tu automatización de fin de ciclo, agrega al final de la secuencia una acción que encienda ese interruptor. En YAML son estas líneas:
- action: input_boolean.turn_on
target:
entity_id: input_boolean.spulmaschine_ausraumen
Paso 3. Un panel con lo pendiente. Crea un dashboard nuevo, por ejemplo con la dirección dashboard-erinnerungen (o dashboard-recordatorios, si cambias también la url de la automatización), y agrega una tarjeta en el editor de código con estas dos baldosas. La roja aparece solo mientras la tarea está pendiente; la otra, cuando ya se cumplió, y las dos muestran desde cuándo:
type: vertical-stack
cards:
- type: tile
entity: input_boolean.spulmaschine_ausraumen
name: Spülmaschine ausräumen – offen
icon: mdi:dishwasher-alert
color: red
state_content: last_changed
tap_action:
action: none
visibility:
- condition: state
entity: input_boolean.spulmaschine_ausraumen
state: "on"
- type: tile
entity: input_boolean.spulmaschine_ausraumen
name: Spülmaschine ausgeräumt
icon: mdi:dishwasher
state_content: last_changed
tap_action:
action: none
visibility:
- condition: state
entity: input_boolean.spulmaschine_ausraumen
state: "off"

Paso 4. La automatización del recordatorio. Crea una automatización nueva, pasa a Editar en YAML y pega el código. Después cambia notify.mobile_app_euer_iphone (aparece cuatro veces) por el servicio de notificación de tu teléfono, que encuentras en Ajustes → Dispositivos y servicios → Mobile App, y pon en tag_id el identificador de tu sticker, no su nombre visible. Los textos de las notificaciones están en alemán: tradúcelos a tu gusto.
alias: Spülmaschine ausräumen – Erinnerung
description: Erinnert alle 30 Minuten, bis der NFC-Sticker an der Spülmaschine gescannt wird.
mode: queued
triggers:
- trigger: state
entity_id: input_boolean.spulmaschine_ausraumen
to: "on"
id: erinnern
- trigger: time_pattern
minutes: "/30"
id: erinnern
- trigger: tag
tag_id: spuelmaschine
id: gescannt
actions:
- choose:
- alias: Erinnern, solange nicht ausgeräumt
conditions:
- condition: trigger
id: erinnern
- condition: state
entity_id: input_boolean.spulmaschine_ausraumen
state: "on"
- condition: time
after: "07:00:00"
before: "22:00:00"
sequence:
- action: notify.mobile_app_euer_iphone
data:
title: Spülmaschine ausräumen
message: Die Spülmaschine ist fertig. Scannt den Sticker, sobald sie leer ist.
data:
tag: spuelmaschine
url: /dashboard-erinnerungen/0
- alias: Sticker gescannt
conditions:
- condition: trigger
id: gescannt
sequence:
- if:
- condition: state
entity_id: input_boolean.spulmaschine_ausraumen
state: "on"
then:
- action: input_boolean.turn_off
target:
entity_id: input_boolean.spulmaschine_ausraumen
- action: notify.mobile_app_euer_iphone
data:
message: clear_notification
data:
tag: spuelmaschine
- action: notify.mobile_app_euer_iphone
data:
title: ✓ Erledigt
message: Spülmaschine als ausgeräumt markiert.
else:
- action: notify.mobile_app_euer_iphone
data:
title: Nichts zu tun
message: Die Spülmaschine war schon als ausgeräumt markiert.

Lo que hace cada parte:
- Los disparadores "interruptor encendido" y "cada 30 minutos" comparten el id
erinnern. Así el primer recordatorio llega apenas termina el ciclo y los siguientes, cada media hora. - La condición de horario (7:00 a 22:00) respeta la noche. Si la máquina termina a las 23:00, el siguiente aviso llega a las 7:00.
- El
tag:dentro de los datos de la notificación no tiene nada que ver con el sticker: le pone nombre a la notificación, así cada recordatorio nuevo reemplaza al anterior en vez de apilarse.clear_notificationborra el último al escanear. urlabre el panel del paso 3 al tocar el recordatorio;/0es la primera vista del dashboard.mode: queuedpone en cola un escaneo que llega justo mientras la automatización manda un recordatorio, en vez de perderlo.
Para probar sin esperar un ciclo completo, enciende a mano el interruptor auxiliar: dentro del horario, el recordatorio llega al tiro, y al escanear recibes la confirmación.


Receta 2: ¿me tomé la pastilla?
Acá no hay máquina que avise: la tarea se abre sola a las 3:00 de la madrugada y los recordatorios empiezan a las 8:00. La gracia está en un segundo ayudante, un input_datetime que guarda la hora de la toma. Si escaneas el sticker por segunda vez, Home Assistant no cambia nada y te responde cuándo fue la última toma, que es justo la pregunta que querías contestar.
Crea dos ayudantes: un interruptor (en el original, input_boolean.tablette_nehmen) y una fecha y hora (input_datetime.tablette_zuletzt_genommen). La automatización se pega igual que en la receta 1, cambiando los mismos datos:
alias: Tablette genommen – Erinnerung
description: Ab 3 Uhr offen, erinnert ab 8 Uhr alle 30 Minuten bis zum Scan. Ein zweiter Scan zeigt, wann die Tablette genommen wurde.
mode: queued
triggers:
- trigger: time
at: "03:00:00"
id: start
- trigger: state
entity_id: input_boolean.tablette_nehmen
to: "on"
id: erinnern
- trigger: time_pattern
minutes: "/30"
id: erinnern
- trigger: tag
tag_id: tablette
id: gescannt
actions:
- choose:
- alias: Neuer Tag
conditions:
- condition: trigger
id: start
sequence:
- action: input_boolean.turn_on
target:
entity_id: input_boolean.tablette_nehmen
- alias: Erinnern, solange nicht genommen
conditions:
- condition: trigger
id: erinnern
- condition: state
entity_id: input_boolean.tablette_nehmen
state: "on"
- condition: time
after: "08:00:00"
before: "22:00:00"
sequence:
- action: notify.mobile_app_euer_iphone
data:
title: Tablette nehmen
message: Die Tablette für heute ist noch offen. Scannt den Sticker an der Box, wenn ihr sie genommen habt.
data:
tag: tablette
url: /dashboard-erinnerungen/0
- alias: Sticker gescannt
conditions:
- condition: trigger
id: gescannt
sequence:
- if:
- condition: state
entity_id: input_boolean.tablette_nehmen
state: "on"
then:
- action: input_boolean.turn_off
target:
entity_id: input_boolean.tablette_nehmen
- action: input_datetime.set_datetime
target:
entity_id: input_datetime.tablette_zuletzt_genommen
data:
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
- action: notify.mobile_app_euer_iphone
data:
message: clear_notification
data:
tag: tablette
- action: notify.mobile_app_euer_iphone
data:
title: ✓ Erledigt
message: "Tablette um {{ now().strftime('%H:%M') }} Uhr als genommen markiert."
else:
- action: notify.mobile_app_euer_iphone
data:
title: Schon erledigt
message: "Die Tablette wurde bereits am {{ as_timestamp(states('input_datetime.tablette_zuletzt_genommen')) | timestamp_custom('%d.%m. um %H:%M') }} Uhr genommen."
Si no tomas la pastilla antes de las 22:00, el interruptor queda encendido y los recordatorios vuelven a las 8:00 del día siguiente. Después de crear la automatización, enciende el interruptor a mano una vez; si no, el primer día queda cerrado hasta las 3:00 y tu primer escaneo respondería que ya estaba hecho. Y una aclaración necesaria: esto ayuda a acordarse, pero no reemplaza las indicaciones de tu médico ni un pastillero con alarma si la toma es crítica.
Límites que conviene conocer
- Un escaneo vale para toda la casa. El primero que escanea cumple la tarea por todos. Para avisar a más de un celular, duplica cada acción
notifycon el servicio del otro teléfono. - Solo cuentan los teléfonos con la app conectada. Un celular sin la app de Home Assistant no dispara nada, y cada teléfono tiene que permitir cada sticker una vez.
- Sin celular, no hay escaneo. Los niños o los adultos mayores sin teléfono quedan fuera, salvo que instales un lector fijo (ver la primera variante).
Variantes y mejoras
- Una estación de registro sin celular. Monta un ESP32 con un lector RC522 o PN532 junto a la puerta o al pastillero y prográmalo con ESPHome: sus componentes
rc522ypn532tienen el disparadoron_tag, que puede llamar ahomeassistant.tag_scannedcon el identificador de la tarjeta. Para Home Assistant es un escaneo más, así que las automatizaciones de arriba funcionan sin tocarlas, y basta acercar una tarjeta o un llavero. - Confirmación en el mismo lugar. Suma un buzzer o un LED a esa estación para que confirme el registro ahí mismo, sin mirar el teléfono.
- Más tareas con el mismo patrón. Cambiar el filtro del purificador, regar las plantas, sacar la basura el día que pasa el camión: cualquier tarea que se hace en un lugar fijo encaja con interruptor, reloj y sticker.
Personalización para Chile
En Chile consigues lo necesario para escribir los stickers en MechatronicStore:
- Módulo NFC RFID PN532 (SKU GQ2-8): el lector y escritor que usa el script de Python por I²C.
- Raspberry Pi 4 Model B de 8GB RAM (SKU M-204): corre el script de escritura (y, si quieres, el propio Home Assistant).
- Cables Hembra Hembra 40 piezas 20cm (SKU C-413): para conectar los pines del PN532 al conector de la Raspberry Pi.
El original usa una Raspberry Pi 5; la 4 funciona igual con el mismo script. Los stickers deben ser NTAG213 (o NTAG215): las etiquetas MIFARE Classic de 1K no sirven para este proyecto, porque el iPhone no las lee. Para la variante sin celular, el Módulo Lector RFID NFC RC522 (SKU X8-8) y un ESP32 del catálogo son el punto de partida.
Recursos
- Tutorial original (alemán): basado en NFC Tags in Home Assistant: Der Cent Trick gegen Vergessen, de Philipp Schweizer en raspberry.tips
- Repositorio GitHub: script y automatizaciones del artículo
- Documentación adicional: integración Tag de Home Assistant, hoja de datos NTAG213/215/216 de NXP y enlaces universales de la app de Home Assistant
Versión chilena con componentes en stock local en MechatronicStore.







