Una falsa alarma que pausa una impresión de nueve horas es peor que no tener vigilante. Con esa regla arriba de la mesa se diseña todo lo que viene: el modelo entrenado en el capítulo anterior separa limpiamente impresiones normales de enredos de filamento, pero un modelo no es un vigilante. Falta la lógica que decide cuándo vale la pena molestar a alguien.
En este capítulo el modelo .eim se convierte en una aplicación real corriendo sobre el Arduino UNO Q: evalúa una imagen de la cámara cada 15 segundos mientras la impresora está imprimiendo, aplica un filtro temporal para que un solo dato raro no dispare nada, y cuando decide que hay problema avisa a Home Assistant por MQTT, enciende la matriz LED y guarda una foto como evidencia. Opcionalmente, pausa la impresora.
Al terminar vas a tener la app instalada y funcionando, vas a saber registrar tu propio modelo de Edge Impulse en App Lab. que es la parte que no está documentada en ninguna parte. y vas a conocer las tres lecciones que salieron del primer día de operación real, incluidas dos que parecen fallas del modelo y en realidad son errores de diseño del sistema alrededor.

Las tres reglas antes de escribir código
Antes del primer archivo conviene fijar el comportamiento, porque de esas decisiones sale toda la arquitectura:
Regla 1. un dato aislado no es una alarma. La app no juzga cada frame por separado. Recién avisa cuando tres de las últimas cuatro revisiones superaron el umbral. Un valor atípico suelto se disuelve solo.
Regla 2. el vigilante solo trabaja cuando hay algo que vigilar. Si la impresora no está imprimiendo, no se evalúa nada. Quién responde esa pregunta: la API de Moonraker de la impresora.
Regla 3. la pausa automática es opt in. Viene desactivada de fábrica y se enciende recién cuando el umbral demostró aguantar varias impresiones sin equivocarse.
De ahí sale un ciclo simple que se repite cada 15 segundos: revisar estado de impresión → obtener imagen → consultar al modelo → aplicar filtro temporal → eventualmente, alarma.
Vale la pena detenerse en por qué el filtro de 3 de 4 es tan efectivo. Si la probabilidad de que un frame normal supere el umbral por casualidad es, digamos, un 2%, la probabilidad de que eso pase tres veces dentro de una ventana de cuatro es del orden de una entre cien mil. Con revisiones cada 15 segundos, esa cuenta es la diferencia entre una falsa alarma cada par de horas y una cada varios meses. En cambio, un problema real. el enredo de filamento no se deshace solo. dispara los cuatro frames seguidos y la alarma llega a los 45 segundos. El costo es ese retardo, y es barato.
Anatomía de una app de App Lab
Una app de Arduino App Lab es una carpeta bajo ~/ArduinoApps/ con una estructura clara. El archivo app.yaml describe la app y lista los bricks: bloques ya hechos que App Lab levanta como contenedores Docker. En python/ vive la parte Linux (nuestra lógica de vigilancia) y en sketch/ la parte de microcontrolador, que corre sobre el STM32 del UNO Q y acá se encarga de la matriz LED como luz de alarma. Los dos mundos hablan por el "bridge": una llamada de función desde Python llega como callback al sketch.
El app.yaml es cortito (recortado a lo esencial):
name: Spaghetti-Waechter
icon: 🍝
bricks:
- arduino:web_ui: # Status-Webseite auf Port 7000
- arduino:visual_anomaly_detection:
model: spaghetti-fomo-ad-v1
Con dos bricks basta: visual_anomaly_detection ejecuta el modelo de Edge Impulse y web_ui sirve después la página de estado.
Instalar la app en la placa
Por SSH, clona el repositorio y copia la carpeta de la app al lugar donde App Lab busca aplicaciones:
git clone --depth 1 https://github.com/raspberry-tips/arduino-uno-q-projects.git
cp -r arduino-uno-q-projects/spaghetti-waechter ~/ArduinoApps/
Después recarga App Lab una vez y la app aparece entre las tuyas con el ícono 🍝. La ventaja del camino por clonado es que te queda el código ahí mismo para mirarlo, que es lo que realmente quieres si vas a adaptar algo.
Registrar tu propio modelo: la parte no documentada
El brick de anomalías trae un modelo de ejemplo (un detector de grietas en hormigón). Cómo meterle un modelo propio de Edge Impulse no estaba documentado en ninguna parte al momento de escribir el original, así que esta es la primera documentación pública del procedimiento.
Hacen falta dos cosas: el archivo .eim exportado en el capítulo anterior (target Linux aarch64) y un pequeño archivo de registro.
El .eim queda descargado en tu computador después del export, así que primero viaja a la placa por SCP. En Windows lo puedes hacer gráficamente con WinSCP (mismas credenciales que SSH: arrastra el archivo al directorio home y renómbralo a spaghetti-fomo-ad-v1.eim). En macOS o Linux es una línea:
scp raspberry-tips-*.eim arduino@<board-ip>:~/spaghetti-fomo-ad-v1.eim
Ya en la placa, el archivo va a su lugar definitivo y necesita permisos de ejecución:
mkdir -p ~/.arduino-bricks/ei-models ~/.arduino-bricks/models/custom-ei/spaghetti-fomo-ad-v1
cp spaghetti-fomo-ad-v1.eim ~/.arduino-bricks/ei-models/
chmod +x ~/.arduino-bricks/ei-models/spaghetti-fomo-ad-v1.eim
Junto a eso creas el registro en ~/.arduino-bricks/models/custom-ei/spaghetti-fomo-ad-v1/model.yaml. Ese archivo le dice a App Lab que el modelo existe, y con qué variable de entorno debe cargarlo cada brick:
id: "spaghetti-fomo-ad-v1"
name: "Spaghetti-Waechter FOMO-AD v1"
runner: "brick"
description: "Anomalie-Erkennung fuer 3D-Druck-Fehler"
bricks:
- id: "arduino:visual_anomaly_detection"
model_configuration:
EI_V_ANOMALY_DETECTION_MODEL: "/home/arduino/.arduino-bricks/ei-models/spaghetti-fomo-ad-v1.eim"
Para confirmar que el registro funcionó, ejecuta arduino-app-cli model list: tu modelo tiene que aparecer junto a los que vienen de fábrica. Desde ese momento lo puedes referenciar con la línea model: en cualquier app.
Importante: apagar el recolector del capítulo 2
La cámara la puede usar uno solo a la vez. El servicio de vista en vivo del capítulo anterior arranca solo en cada boot; si sigue corriendo, la app no recibe frames y en el log solo vas a ver No camera frame received. Antes del primer arranque de la app, una vez:
sudo systemctl disable --now liveview capture
El disable es la parte importante: un stop a secas dura hasta el próximo reinicio nomás. Si más adelante necesitas la vista en vivo fluida para reencuadrar la cámara, detén la app, ejecuta sudo systemctl start liveview y después revierte. En el día a día casi no hace falta, porque la página del vigilante muestra la imagen de la cámara igual.
Home Assistant: MQTT Discovery y una trampa de usuarios
La app se anuncia sola a Home Assistant por MQTT Discovery. Aparece automáticamente un dispositivo "Spaghetti Waechter (UNO Q)" con cuatro entidades: un sensor de alarma (device_class: problem, listo para automatizaciones y notificaciones), el score de anomalía actual como medición, el estado del vigilante (idle / warmup / watching) y una entidad de cámara "Alarm Bild", que al saltar la alarma pone la foto de evidencia en el dashboard. Configuración manual en Home Assistant: ninguna.

La trampa está en el detalle de la autenticación. El add on Mosquitto no trae cuentas propias de fábrica: autentica contra los usuarios de Home Assistant. Y en Home Assistant "personas" y "usuarios" son dos cosas distintas. una persona creada sin cuenta de usuario no puede conectarse al broker.
El camino limpio: en Ajustes → Personas → pestaña Usuarios, crea un usuario propio, puramente local, dedicado solo a MQTT, y usa esas credenciales en la app.

Moonraker: la impresora cuenta cuándo está imprimiendo
¿Cómo sabe la app que se está imprimiendo? Le pregunta a la API de Moonraker, el servidor HTTP de Klipper que viene corriendo de fábrica en impresoras con ese firmware (en el equipo del proyecto, una Elegoo Neptune 4 Plus, en el puerto 80 y sin autenticación dentro de la red local; Mainsail y Fluidd hablan con la impresora por esta misma API).
La pregunta "¿estás imprimiendo?" es un GET simple a /printer/objects/query?print_stats. La app la hace en cada ciclo y usa dos campos de la respuesta: state, que decide si se evalúa o no, y print_duration, el tiempo puro de impresión, que resulta clave para una de las lecciones de más abajo.

Para la pausa automática basta un POST a /printer/print/pause, que es una pausa ordenada con el cabezal estacionado. deliberadamente no un paro de emergencia. La IP de tu impresora la ingresas cómodamente en el panel de ajustes de la página web del vigilante, campo Moonraker host.
¿Y si no tienes una impresora con Klipper? La integración con Moonraker es opcional: apaga el interruptor only check while printing en el panel de ajustes y el vigilante evalúa de forma permanente, sin API de impresora. Pierdes solo dos comodidades: el inicio y detención automáticos de la vigilancia, y la pausa automática.

Y si tu impresora usa otra API (OctoPrint y compañía), moonraker.py son unas 50 líneas y es el único punto del código que hay que cambiar.
El primer arranque: Docker descarga, el sketch se graba
Se parte con un clic: abre la app "Spaghetti Waechter" en App Lab y presiona el botón Run (▶) de arriba. En el primerísimo arranque pasan varias cosas: App Lab compila el sketch y lo graba en el STM32 a través del adaptador de depuración interno, descarga las imágenes Docker de los bricks (el runner del modelo de Edge Impulse pesa unos cuantos cientos de megabytes, así que toma unos minutos) y después levanta los contenedores.
Puedes leer todo en vivo en la consola de App Lab, o por SSH con arduino-app-cli app logs user:spaghetti-waechter --follow. Si todo salió bien, ahí aparecen las tres líneas más lindas del día: el runner del modelo en Healthy, App started y MQTT connected.
Lección 1: la pipeline de entrenamiento y la de inferencia tienen que ser idénticas
Llegó la primera impresión de prueba, y con ella el balde de agua fría: todas las imágenes quedaron sobre el umbral de alarma, en una impresión perfectamente normal.
La imagen de depuración lo explicó de inmediato: estaba azul intenso. La app tomaba sus frames al principio a través del periférico de cámara de App Lab, que renderiza con un balance de blancos distinto al de la pipeline de GStreamer con la que se juntaron todas las imágenes de entrenamiento del capítulo 2. Los canales rojo y verde coincidían casi exactamente; el azul venía amplificado al doble. Para el modelo, cada imagen se veía distinta a todo lo que había aprendido.
Acá está el principio general que conviene guardarse para cualquier proyecto de visión con IA: la inferencia tiene que pasar por exactamente la misma pipeline de imagen que el entrenamiento. Misma resolución, misma rotación, mismo balance de blancos, idealmente el mismo código.
Esto no es una manía: es lo que en machine learning se llama domain shift. FOMO AD aprendió la distribución de los parches de tus imágenes de entrenamiento, y esa distribución incluye las estadísticas de color del sensor tal como las procesó esa pipeline. Duplicar el canal azul desplaza cada parche lejos de todo lo aprendido, y el modelo hace exactamente lo que le pediste: reportar que la escena no se parece a lo normal. El modelo estaba bien; el sistema alrededor, mal.
La solución fue que la app arranque internamente la misma pipeline de GStreamer del capítulo 2 como flujo continuo. el balance de blancos necesita unos segundos para estabilizarse, por eso no sirve el enfoque de foto suelta. y lea sus frames desde ahí. Después de eso: colores correctos, problema resuelto.
La primera captura real
Y entonces todo pasó más rápido de lo planeado. Mientras se reconstruía la pipeline, la impresora. probablemente por filamento que absorbió humedad. empezó a producir spaghetti en serie. Cinco impresiones fallidas en una mañana, y el vigilante reportó cada una: score sobre 50, alarma en Home Assistant, matriz LED en rojo, foto de evidencia guardada. Lo que estaba planificado como sesión de calibración se transformó sin querer en la primera prueba de fuego. Aprobada.

Especialmente útil: la app dibuja sobre la imagen de alarma qué regiones molestaron al modelo. FOMO AD no evalúa la imagen como un todo, sino en una grilla; las celdas llamativas las entrega el brick como bounding boxes y una función auxiliar incluida (draw_anomaly_markers) las pinta sobre la foto. De un vistazo sabes si la alarma apunta al ovillo de filamento o a otra cosa completamente distinta.

Ese detalle vale más de lo que parece: convierte una alarma en algo auditable. Sin los marcadores, una alarma es un número y una foto, y tú tienes que adivinar qué vio el modelo. Con ellos puedes distinguir en dos segundos entre "detectó el enredo" y "detectó tu mano entrando al cuadro".
Lección 2: el arranque necesita un período de gracia
La segunda lección llegó con la siguiente impresión: alarma, cuando todavía no se había impreso nada.
La causa, siendo honestos, es un hoyo en los datos de entrenamiento. Durante el homing y la línea de purga, la cama se va completamente hacia adelante y la cámara ve de repente el marco de la impresora, las guías y la mesa de madera. Imágenes así prácticamente no existían en el entrenamiento, porque el recolector siempre partía cuando la impresión ya estaba en curso. Para el modelo, la fase de arranque es lo más anómalo que existe.
La app ahora tiene un bloqueo de calentamiento: durante los primeros 60 segundos de tiempo puro de impresión (el que entrega print_duration de Moonraker. el calentamiento de la cama no cuenta) los scores se calculan y se muestran, pero no se consideran para la alarma. 60 segundos cubren homing y purga, y dejan la primera capa. donde nacen la mayoría de los spaghetti. ya vigilada.
Fíjate en el detalle de usar print_duration y no el reloj: si contaras desde que la impresora recibe el trabajo, el precalentamiento de una cama grande se comería la ventana completa y quedarías vigilando desde la capa tres.
La página de estado: log, imagen en vivo y ajustes en el navegador
Leer logs por SSH sirve para la instalación, no para el día a día. Por eso la app tiene el segundo brick: web_ui sirve en el puerto 7000 una página de estado, deliberadamente simple. consulta un único endpoint JSON cada cinco segundos, sin complicarse con WebSockets. La abres en http://<IP-de-tu-placa>:7000, y la dirección exacta la escribe la app al arrancar en la consola de App Lab (línea Network URL).
Ahí ves un semáforo de estado (idle / warmup / watching / ALARM), el score actual con su umbral, la imagen de cámara del momento, la última imagen de alarma con sus marcadores y un log de eventos corriendo.

Como los umbrales se tocan bastante más seguido de lo planeado, todos los ajustes se mudaron también a la página: umbral, intervalo de revisión, ventana de alarma, bloqueo de calentamiento, los interruptores de pausa automática y de recolección de imágenes de entrenamiento, más los datos de conexión de Moonraker y MQTT, cada uno con un ícono de información que explica qué hace. Se guarda todo en un archivo JSON dentro de la carpeta de la app, que sobrevive a los reinicios, y los cambios aplican de inmediato (solo MQTT necesita reiniciar la app).

Una advertencia honesta: la página no tiene login. Cualquiera en tu red puede abrirla y ver ahí también la contraseña de MQTT. Para una red doméstica de proyectos está bien, pero usa un usuario MQTT exclusivo del vigilante y una contraseña que no ocupes en ningún otro lado. Si tu router lo permite, dejar la placa en una VLAN de invitados o de IoT y permitir el puerto 7000 solo desde tu equipo de escritorio cuesta diez minutos y cierra el tema.
Bonus: la app junta sus propios datos de entrenamiento
Una función se impuso sola durante las pruebas: si la app igual evalúa una imagen cada 15 segundos, bien puede quedársela. El buffer de entrenamiento guarda en rotación cada imagen sin novedad. máximo 2.500, las más antiguas se van. así que cuando necesites reentrenar (iluminación nueva, cámara movida, otro filamento) el material ya está listo y viaja a Edge Impulse con el script de subida del capítulo anterior.
Detalle importante: solo se recolectan imágenes bajo el umbral de alarma. Si no, durante una impresión fallida se colarían fotos de spaghetti etiquetadas como "normal" y envenenarían el modelo en el siguiente entrenamiento. El círculo con el recolector del capítulo 2 se cierra: la app es ahora su propio recolector de datos.
Lección 3: el modelo incluye la posición de la cámara
La tercera lección es la más importante. Manipulando la impresora, la cámara se movió mínimamente. y los scores de impresiones normales subieron de menos de 33 a entre 45 y 50, de forma permanente.
FOMO AD no aprende "cómo se ven los spaghetti", aprende "cómo se ve mi escena normalmente". La posición de la cámara y la luz son parte del modelo. Unos pocos grados de desviación y la calibración deja de valer.
Eso no es una debilidad del concepto, pero sí una exigencia clara al hardware: el soporte de la cámara tiene que quedar absolutamente repetible, o la luz tiene que ser constante. de preferencia las dos cosas.
Puesto en términos del capítulo anterior: mover la cámara desplaza el corredor entre el percentil 99 de lo normal y el mínimo de las fallas. Si lo desplaza hacia arriba, tu umbral queda demasiado bajo y empiezan las falsas alarmas; si lo desplaza hacia abajo, el umbral queda alto y se te escapan fallas reales. Por eso, cada vez que toques la cámara o cambies la iluminación, lo correcto es recolectar unos cuantos scores de una impresión normal y volver a mirar el corredor antes de confiar en la alarma.
El balance después del primer día en operación real es bueno igual: concepto probado. El vigilante detectó y reportó cinco fallas reales, cada falsa alarma tuvo una causa entendible, y la cadena completa desde la cámara hasta la foto de evidencia en el dashboard de Home Assistant funciona.
Preguntas frecuentes
¿Funciona con OctoPrint u otras impresoras? La detección de impresión usa la API de Moonraker, así que anda con cualquier impresora Klipper. Para OctoPrint habría que cambiar la clase pequeña de Moonraker por la API REST de OctoPrint; el resto de la app es independiente de la impresora. Y sin API de impresora también funciona: apaga only check while printing y el vigilante evalúa permanentemente.
¿Necesito Home Assistant sí o sí? No. Sin broker MQTT la app corre igual: página de estado, alarma en la matriz LED, fotos de evidencia y pausa opcional funcionan independientemente. Home Assistant solo la hace más cómoda: notificaciones push, tarjeta en el dashboard y la imagen de alarma como entidad de cámara.
¿Puedo usar el modelo ya entrenado en vez de entrenar el mío? Lamentablemente no, y ese es el núcleo de FOMO AD: el modelo aprende cómo se ve tu escena concreta, incluyendo impresora, posición de cámara y luz. Un modelo ajeno no sirve en otro montaje. Pero entrenar es la parte fácil de la serie: las imágenes se juntan solas y el entrenamiento toma alrededor de una hora.
¿Pausa la impresora por su cuenta? Solo si tú quieres. La pausa automática es un interruptor opt in, apagado de fábrica. Actívalo recién cuando el umbral haya aguantado varias impresiones sin falsas alarmas. E incluso entonces, la app manda un comando normal de pausa a Moonraker, con el cabezal estacionado. nunca un paro de emergencia.
Variantes y mejoras
- Alarma por Telegram o ntfy sin Home Assistant: si no tienes un servidor de domótica andando, el mismo punto del código que publica en MQTT puede hacer un POST a la API de un bot de Telegram o a un tópico de ntfy, adjuntando la foto de evidencia. Son unas pocas líneas y te deja la notificación en el celular sin infraestructura adicional.
- Cortar la impresión de verdad, no solo pausarla: si te preocupa el cabezal más que la pieza, agrega un relé o un enchufe inteligente controlado desde la misma automatización de Home Assistant que ya recibe el sensor de alarma. Ojo: cortar la energía de golpe deja la boquilla caliente llena de filamento; es una decisión de compromiso, no una mejora obvia.
- Graficar el score a lo largo de la impresión: manda el score a InfluxDB o déjalo en el recorder de Home Assistant y hazle un gráfico. La curva completa de una impresión enseña más sobre el umbral correcto que cualquier valor puntual, y hace evidente si el score sube de a poco a medida que la pieza crece. que es una señal legítima, no una falla.
- Replicar el vigilante con una ESP32-CAM: para un montaje más económico, una ESP32-CAM puede tomar la foto y publicarla por MQTT, dejando la inferencia en un mini PC o en el propio servidor de Home Assistant. Pierdes la matriz LED integrada y la latencia sube, pero el filtro temporal de 3 de 4 y el bloqueo de calentamiento se implementan igual del lado del servidor, que es donde vive la lógica que realmente importa.
- Segunda cámara para vista lateral: los enredos de filamento se ven distinto desde arriba que desde el costado. Dos modelos independientes, uno por ángulo, y una alarma que exige coincidencia de ambos, bajan aún más las falsas alarmas a costa de un poco más de hardware.
Personalización para Chile
El montaje completo de la serie usa estos componentes:
- Placa principal Arduino UNO Q. corre la app, el modelo
.eimy la matriz LED de alarma. - Media Carrier para Arduino UNO Q. expone el conector CSI para la cámara.
- Cámara IMX219 (Raspberry Pi Camera v2). el sensor con el que se entrenó el modelo.
- Cable plano FFC de 15 pines para cámara CSI. elige el largo según dónde montes la cámara.
- Fuente USB-C 5V 3A. la placa con cámara y contenedores Docker corriendo pide corriente de verdad.
- Impresora 3D FDM con Klipper y Moonraker. el equipo a vigilar; la integración de estado y pausa depende de esta API.
- Iluminación constante (tira LED con su fuente). la luz es parte de lo que el modelo aprende como normal, así que es tan importante como la cámara.
- Soporte de cámara impreso en 3D. lo fabricas con la misma impresora; la lección 3 dice que mientras más rígido y repetible, mejor.
En MechatronicStore consigues localmente la mayoría de estas piezas. cámaras con conector CSI, cables FFC, fuentes USB-C, tiras LED con fuente y placas de desarrollo. sin importación ni esperas largas. Si el Arduino UNO Q no está disponible, la ruta equivalente es una Raspberry Pi 5 con la misma cámara IMX219: el modelo se exporta igual para Linux (AARCH64), la app en Python corre sin cambios y lo único que pierdes es la matriz LED integrada, que se reemplaza con una matriz MAX7219 o un par de LEDs.
Nota de esta versión: los enlaces directos a producto con precio y SKU no pudieron generarse en esta corrida porque el catálogo no estaba disponible al momento de escribir. Busca los componentes por su nombre en la tienda; las equivalencias descritas arriba se mantienen.
Recursos
- Tutorial original (alemán): KI Druckwächter mit dem Arduino UNO Q (Teil 4): Die Wächter App mit App Lab, por Philipp Schweizer en raspberry.tips
- Repositorio GitHub del proyecto: raspberry tips/arduino uno-q-projects
- Capítulo anterior (entrenamiento del modelo): Anomalie Modell trainieren mit Edge Impulse
- Documentación de la API de Moonraker: moonraker.readthedocs.io
- MQTT Discovery en Home Assistant: documentación oficial
Versión chilena inspirada en el trabajo original, con componentes en stock local en MechatronicStore.




