TFG · Ingeniería de Telecomunicaciones · UC3M
Registro cronológico del trabajo realizado en el desarrollo del sistema de búsqueda y rescate basado en UAV.
Con la entrega de la memoria del TFG el 7 de septiembre, el proyecto queda cerrado y validado de extremo a extremo. Guardian Eye es ya un dron autónomo de búsqueda y rescate, construido y probado en vuelo real, con toda la cadena funcionando junta: percepción visual a bordo, arquitectura IoT en la nube y teleoperación remota.
El dron detecta personas desde el aire con un modelo propio (YOLO11), entrenado sobre un dataset aéreo construido desde cero y ejecutado a bordo sobre el acelerador Hailo-8L, prácticamente en tiempo real. La telemetría se reparte en cuatro dominios independientes (ambiental, sistema, vuelo y detección) con almacenamiento local store-and-forward, y viaja por MQTT hasta un servidor en AWS (InfluxDB y Grafana) sin perder un dato ante cortes de red. Las comunicaciones combinan mando RC directo, telemetría SiK de 433 MHz, enlace 4G/LTE y una VPN en malla con Tailscale, sobre la que se teleopera el dron desde un panel web (PWA) —armado, despegue, misión y vídeo en directo— a través de una cadena de mando API REST → MQTT → MAVLink.
La validación siguió una estrategia escalonada: simulación SITL, banco de integración con la Pixhawk y, por último, un vuelo real en el club Alas de Galapagar, donde el sistema detectó a las personas presentes desde el aire. El detector es una prueba de concepto, con recorrido de mejora por delante, pero el trabajo demuestra que es viable integrar inferencia edge acelerada, comunicaciones de largo alcance y una arquitectura IoT distribuida en una única plataforma de vuelo.
Filtrar por área
Julio 2026
1883 para la comunicación MQTT con el broker Mosquitto, puerto 22 para SSH y puerto 8086 para el acceso a la base de datos InfluxDB.sensor.py en la Pi y mqtt_to_influx.py en la EC2.sensor.py para que ningún dato se pierda ante una caída temporal de la conexión (cada lectura se almacena primero en una base de datos local SQLite, buffer.db).mqtt_to_influx.py actúa como cliente suscriptor; se ejecuta en la EC2 dentro de un entorno virtual propio (env) con las librerías paho-mqtt (comunicación con el broker) e influxdb-client (escritura en la base de datos).8086, de forma que se pueda acceder desde el navegador mediante la dirección pública de la EC2..gitignore (evita subir archivos sensibles o innecesarios), requirements.txt (dependencias reproducibles) y .env.example (plantilla de credenciales, que se mantienen fuera del repositorio).
gorostiditfg.com en Cloudflare 10,41 $, para acceder a los servicios mediante subdominios legibles en vez de la IP del servidor.www.gorostiditfg.com y control.gorostiditfg.com.8086 en la petición HTTP, había que indicarlo y ya no es necesario especificarlo → influxdb.gorostiditfg.com..ssh en la EC2 para permitir el acceso con mi clave pública en lugar del .pem (habilitando esta otra forma editando el .ssh).14550.8086 del grupo de seguridad de la EC2, ya innecesario: el acceso a la interfaz de InfluxDB se realiza ahora a través de nginx configurado como reverse proxy (influxdb.gorostiditfg.com), sin exponer el puerto directamente a internet.127.0.0.1, el reenvío UDP y el puerto 14550.udpin/udpout) y por qué en MAVLink la Pi escucha mientras recibe la telemetría del autopiloto.comandos.py, en el EC2) que envía las órdenes por MQTT, y de un receptor (receptor.py, en la Pi) que las traduce a MAVLink y las ejecuta sobre el dron simulado.api.py, en el EC2) para poder invocar los comandos por HTTP en lugar de por terminal, y para ello se ha abierto el puerto 5000 en el grupo de seguridad de AWS.api.py) para entender no solo qué hace, sino por qué existe y qué papel tiene dentro de la arquitectura. Se ha revisado la cadena completa de comunicación: navegador (HTTP) → api.py en el EC2, que traduce HTTP a MQTT → broker Mosquitto (puerto 1883) → receptor.py en la Raspberry Pi, que traduce MQTT a MAVLink → autopiloto → dron.
api.py y receptor.py no están conectados directamente: se comunican a través del broker, gracias al modelo publicador/suscriptor desacoplado de MQTT.comandos.py y api.py son alternativas paralelas para enviar los mismos comandos, no pasos consecutivos.5000 y Mosquitto en el 1883. receptor.py no abre ningún puerto porque actúa como cliente, no como servidor.Ctrl+C incompleto. Resuelto con pkill -f api.py.api.py y receptor.py en servicios systemd, igual que ya lo están mqtt_to_influx.py y sensor.py, para que arranquen solos y no dependan de una terminal abierta. PENDIENTE443 en el grupo de seguridad del servidor.api.gorostiditfg.com en Cloudflare en modo Proxied, configurar nginx como reverse proxy hacia localhost:5000 y actualizar la URL del fetch en el JavaScript del panel. PENDIENTEwww/web/, control/ y api-rest/) e incluyendo las páginas HTML, que hasta ahora solo existían en el servidor.sudo poweroff. Se ha intentado volver a cargar la imagen de Raspberry Pi OS en la tarjeta y no es capaz de leerla.sudo poweroff y mantener todo actualizado en GitHub, para no perder los códigos ni el trabajo hecho hasta el momento.receptor.py, único script que no estaba subido al repositorio y que se perdió con la tarjeta.api.gorostiditfg.com, configurado nginx como reverse proxy hacia localhost:5000, emitido el certificado TLS y actualizada la URL del fetch en el panel de control.receptor.py en la Raspberry Pi y para api.py en la instancia EC2, de forma que ambos arranquen automáticamente y se reinicien ante cualquier interrupción, sin depender de una sesión SSH abierta.Agosto 2026
best.pt).patience=20); la métrica de validación se estabiliza en torno a la época 40, por lo que ese número resulta suficiente para la convergencia.output.mp4. La he probado tanto con un vídeo grabado con el móvil como con uno grabado desde el dron.sar/{dron_id}/{dominio}) y client_id como {dron_id}-{dominio} para que Mosquitto no tire conexiones por IDs duplicados.dron_id desde un .env compartido.vuelo.py y deteccion.py mediante escrituras atómicas en posicion_actual.json.ambiental.py para leer los datos del sensor BME680.sistema.py para monitorizar las métricas y el estado de la Raspberry Pi.vuelo.py de forma provisional con telemetría simulada (a la espera de integrar la Pixhawk).deteccion.py integrando YOLO, afinando parámetros como flags explícitos (--mqtt y --preview), un --anti-spam por defecto a 5 s y haciendo que cada proceso infiera automáticamente su base de datos según el dominio, sin configuraciones extra.sar/#: así identifica automáticamente de qué sensor viene el dato leyendo el topic y simplifica la estructura del JSON para que la base de datos lo trague sin procesado extra.deteccion.py con YOLO sobre un vídeo real, corriéndolo en mi propia máquinasar/{dron_id}/deteccion (con buffer store-and-forward), que incluye la caja en píxeles, la posición del dron y la foto.API_BASE del index.html seguía apuntando por HTTP directamente a la IP pública de la instancia EC2. Como el navegador bloquea las llamadas HTTP inseguras, cambio la URL para que apunte a https://api.gorostiditfg.com.drone_id en el cuerpo de la petición. La API asigna por defecto dron-01, pero como el receptor está escuchando en dron-02, el comando se pierde y nunca llega a ejecutarse.dron-01 / dron-02) en el panel de control.enviar() para que capture el valor seleccionado y lo adjunte en cada petición a la API.receptor.py falla al arrancar con WinError 10013 porque Mission Planner tiene ocupado el puerto 14550. Cambio la conexión a MAVLINK_CONN = "udpin:127.0.0.1:14551".wait_heartbeat() porque el reenvío UDP de Mission Planner sigue apuntando al puerto antiguo. Entro al menú avanzado de Mission Planner (Ctrl + F) y redirijo el forward UDP a 127.0.0.1:14551.TFG ·, definición de topics y creación del README del perfil con badges.TFG ·, definición de topics y creación del README del perfil con badges.requirements.txt las dependencias necesarias para dejar el entorno completo.sistema.py por separado en mi equipo y en la Pi, y confirmo que ambos flujos llegan bien a la base de datos, uno identificado como dron-01 y otro como dron-02.sensores y no había llegado a renombrar; ahora se llama telemetria..pt (pesos nativos de PyTorch, muy completos pero pesados), .onnx (formato "puente" universal, más ligero de cargar), la versión .onnx cuantizada a INT8 (menor tamaño y, en teoría, más velocidad en CPU) y .ncnn (motor optimizado para CPUs ARM como la de la Pi). Dejo también situado el .hef, formato compilado exclusivo del Hailo que solo se puede generar en un PC Linux/x86_64 (a la Pi solo se manda el .hef ya hecho).conversion/ con tres utilidades para regenerar cada formato a partir de weights/best.pt siempre que reentrene: exportar_onnx.py, cuantizar_onnx.py y exportar_ncnn.py.deteccion.py el parámetro --runtime (pt / onnx / onnx-int8 / ncnn) para elegir en cada ejecución con qué motor cargo el modelo, sin tocar el código.vuelo1) y mido en cada uno rendimiento y consumo de CPU..onnx a pelo ronda los 5,4 FPS pero la CPU se va casi al 98%. Con la versión INT8 el consumo baja a la mitad pero el rendimiento no mejora (~5,1 FPS). Con .ncnn saco claramente el mejor resultado: unos 8 FPS de media y en torno a un 55% de CPU.start_recording, deteccion.py no arranca nada y se queda solo escuchando.dronsar/{dron_id}/video/resumen) con la duración, el total de frames, el rendimiento (runtime, FPS y latencia) y las detecciones (alertas y confianza media).--preview a false en deteccion.py: en la Raspberry normalmente no habrá pantalla conectada, así que abrir una ventana no tiene sentido y puede fallar sin entorno gráfico. Se puede forzar a true a mano el día que lo pruebe con un monitor.deteccion.py, además de grabar el vídeo en local, lo emite en paralelo por RTSP (streaming.py), y MediaMTX lo retransmite como WebRTC para verlo en el navegador. Va en baja resolución y es un extra que nunca tumba la detección: si falla el streaming, la grabación y las alertas siguen igual.webrtcAdditionalHosts.stream.gorostiditfg.com, dado de alta en Cloudflare, y abro únicamente el puerto 8189/UDP en el grupo de seguridad. La Raspberry publica por RTSP (8554) a través de Tailscale, así que ese tramo va cifrado por la VPN sin exponer el RTSP a internet..env, incluidos los parámetros del streaming (STREAM_HOST/USER/PASS/PATH) y las carpetas VIDEOS_DIR/FOTOS_DIR.vuelo1.mp4 que estaba por defecto: ahora por defecto va siempre la cámara y el runtime queda en ncnn. La pantalla se queda en negro hasta pulsar "Empezar grabación".yolo11n), la que voy a desplegar a bordo: más ligera y rápida, con recall 0,601.best.pt en Drive en cuanto termino cada entrenamiento..hef y ejecuto la inferencia en la NPU Hailo-8L.TELEM3), con las hélices retiradas por seguridad.Septiembre 2026
Base sobre la que arranca el proyecto. En lo normativo: certificado A1/A3 de AESA y número de operador obtenidos, e inscripción como socia en el club Alas de Galapagar para volar sin necesidad de permisos adicionales y con el seguro incluido. En lo técnico: compra de la Raspberry Pi 5 y del sensor ambiental BME680 con la conexión ya configurada, una instancia EC2 en AWS con el broker Mosquitto e IP elástica, y el primer flujo de datos de extremo a extremo (sensor.py → MQTT → mqtt_to_influx.py → InfluxDB) con arquitectura store-and-forward para no perder lecturas. Todo ello arrancando solo mediante servicios systemd, publicado en dos repositorios de GitHub, sobre el dominio propio gorostiditfg.com y con dos primeras páginas web (presentación y control) ya creadas.
Primera semana completa de desarrollo: servidores web con nginx y subdominios propios (www, control, influxdb) sobre el dominio de Cloudflare, y limpieza de la gestión de claves SSH. Primer vuelo real de un dron en el club de Galapagar, con el número de operador ya pegado en el aparato. El control autónomo del dron simulado se lleva a la Raspberry Pi por MAVLink (arquitectura edge-first), y sobre esa base se monta el control por MQTT y una API REST con panel web de botones para teleoperar el dron. Se activa HTTPS en todas las páginas —apareciendo el primer bloqueo por mixed content— y se reorganiza el repositorio del servidor en GitHub. La semana se cierra con la detección de un fallo en la tarjeta microSD de la Raspberry Pi.
Recuperación completa tras el fallo de la tarjeta microSD: nueva tarjeta grabada, repositorio clonado desde GitHub y receptor.py reescrito por ser el único script que no estaba respaldado. Comienza la redacción de la memoria del TFG (arquitectura IoT con EC2, BME680 y MQTT), y se resuelve definitivamente el problema de mixed content creando el subdominio api.gorostiditfg.com con reverse proxy en nginx y certificado TLS.
Semana centrada en la memoria del TFG: reorganización de capítulos (control de vuelo separado de la arquitectura IoT), reconstrucción del estado del arte, redacción de la normativa (Reglamento UE 2019/947, categoría A3, Remote ID, RD 517/2024) y bibliografía en formato IEEE. También primer contacto con Roboflow de cara al futuro dataset de detección de personas.
Un socio del club presta un hexacóptero de fibra de carbono para reutilizar piezas, lo que permite definir el plan de compra de componentes nuevos (Pixhawk 6C, GPS, telemetría, batería LiPo) y contactar con RC-Innovations. En paralelo, primera práctica completa con Roboflow y Ultralytics: dataset de prueba con frutas, entrenamiento de un primer modelo YOLO en local, ajuste del número de épocas, inferencia sobre vídeo y publicación del código en GitHub.
Semana de mucho avance: se recibe presupuesto y se reserva el kit de vuelo con RC-Innovations, se graban los primeros vídeos reales para el dataset de detección de personas (35 imágenes) y se diseña e implementa la arquitectura de datos definitiva del nodo edge, con un proceso independiente por dominio (ambiental, sistema, vuelo, detección) y topics MQTT jerárquicos. Se prueba con éxito la detección de personas con YOLO sobre vídeo real, y se depuran varios fallos del panel de control web (mixed content, pérdida del drone_id, conflicto de puertos con Mission Planner), añadiendo un selector para elegir el dron. La semana se cierra con la publicación de la landing page de colaboración y el arranque de la web técnica del proyecto (Guardian Eye).
Continúa y se cierra la web técnica del proyecto (Guardian Eye), con las secciones de arquitectura, inteligencia artificial y proyecto ya desarrolladas y enlazadas con la landing, GitHub y el journal. Se reorganiza la memoria separando la adquisición de datos IoT (cap. 6) de los servicios web y control remoto (cap. 7), y se despliega conectividad robusta en el nodo edge: una VPN en malla Zero Trust con Tailscale y un enlace 4G/LTE con SIM de DIGI como salida a Internet principal.
Semana de cierre de la parte de programación: se verifica la ingesta multi-dron de extremo a extremo (dron-01/dron-02) y se renombra el bucket de InfluxDB a telemetria, se termina de construir el dron definitivo, y se compara a fondo el rendimiento de los formatos de despliegue del modelo (PyTorch, ONNX, ONNX INT8 y NCNN), quedándose con NCNN para la Raspberry Pi. Se añade grabación por sesiones controlada por MQTT desde el panel, con JSON de resumen al finalizar cada vídeo, y streaming en directo de la cámara vía MediaMTX y WebRTC sobre un subdominio propio con TLS. La web de control se convierte en PWA, se configura la cámara USB en la Pi, y se da por cerrada toda la parte de programación, quedando todo publicado en GitHub.
Semana de puesta a punto del detector y salto al hardware real. Se amplía el dataset aéreo de personas en Roboflow partiendo de 659 imágenes, priorizando las detecciones a mayor distancia, y se entrena YOLO11 en Google Colab con GPU: la variante small sube el recall de 0,542 a 0,659, y se entrena también la variante nano (yolo11n) como modelo a desplegar a bordo. Sobre la Raspberry Pi se comparan los motores de inferencia en CPU (PyTorch, ONNX FP32, ONNX INT8 y NCNN), con NCNN de nuevo como mejor opción (≈8,3 FPS), y se cierra la comparativa compilando el modelo a .hef para la NPU Hailo-8L: 29,49 FPS y 33,91 ms de latencia, el único formato que sigue en tiempo real a la cámara. La semana termina con el banco de integración semi-HITL —Raspberry Pi 5 conectada a la Pixhawk 6X por UART, validando la cadena de mando completa sobre la electrónica real— y la preparación del primer vuelo integrado (misión en Mission Planner, MTOW de 1.817 g, batería externa para la Pi y cámara USB de respaldo).
El vuelo real en Alas de Galapagar con el sistema integrado completo a bordo: YOLO infiere en tiempo real sobre el vídeo de la cámara y el modelo detecta a las dos personas del campo de visión generando alertas georreferenciadas. El enlace 4G aguanta a baja altura y, aunque el streaming se corta hacia los 10 m, la grabación local y el store-and-forward funcionan sin fallos; las vibraciones del vuelo dejan anotada la necesidad de un gimbal a futuro. Con los resultados del vuelo en la mano arranca la redacción final de la memoria: pipeline de detección y construcción del dataset (Cap. 8), comparativa de motores y despliegue en el Hailo-8L (8.6), validación experimental (Cap. 9), y el marco regulador y socioeconómico junto con las conclusiones, el resumen y los agradecimientos (Caps. 10 y 11).
Repaso final y cierre del documento: bibliografía en formato IEEE, índices de tablas y figuras cuadrados con el contenido real, y una última lectura completa comprobando la coherencia de figuras y tablas, actualizando la normativa tras la sentencia del TS y aplicando el formato UC3M con la Declaración de Uso de IA Generativa. El 7 de septiembre se entrega la memoria del TFG y el proyecto queda cerrado y validado de extremo a extremo.