Carta de presentación
Soy un desarrollador FullStack sénior con más de 25 años de experiencia, apasionado por la tecnología y por trabajar con las personas.
Mi trayectoria incluye también la dirección técnica y el liderazgo de proyectos en España y Brasil, lo que me ha dado una visión amplia, madura y adaptable del mundo del software.
Destaco por mi forma de trabajar: resuelvo problemas complejos con calma, especialmente en situaciones críticas, y mantengo siempre un enfoque en la arquitectura limpia, la eficiencia del código y el rendimiento. Me integro con facilidad en los equipos, colaboro, escucho, ayudo y mantengo una actitud positiva incluso en los momentos difíciles.
Además de mis habilidades técnicas, aporto una dimensión humana que valoro tanto como el código: responsabilidad, empatía, buena comunicación y capacidad para generar confianza y buen ambiente. Hablo español, catalán, inglés y portugués con fluidez, lo que me permite colaborar de manera natural en entornos multiculturales.
En resumen: soy un profesional sénior con valores, trato humano y un enfoque colaborativo, alguien con experiencia real y con quien resulta fácil y agradable trabajar.
Idiomas
Experiencia
-
Senior Fullstack Developer — Biogyne SpainPHP Laravel Vue.js Bootstrap Node.js
-
CTO y Senior Developer — Denusa (Destilaria Nova União S/A - Brasil)PHP C# Laravel Zend Framework Bootstrap Vue.js
-
Senior Developer — Segundamano.esPHP C C++ Perl Bash JavaScript C#
-
Responsable de TI — Plastificados de TerrassaPHP C#
-
Empresa Familiar / Desarrollador — NELOSAPHP Visual Basic
Sobre Mí
Educación
- Grado en Ingeniería Informática
Habilidades
Idiomas
Contacto
Dron autónomo con detección de personas por IA
Un quadcopter construido desde cero: controlador de vuelo Pixhawk con ArduCopter, una Raspberry Pi 4 como cerebro embarcado, una cámara de IA (Sony IMX500) que detecta personas en su propio chip, vídeo en vivo y control de vuelo completo desde una app móvil propia — todo sobre WiFi local, sin mando de radio tradicional ni servicios en la nube.
Es un proyecto personal para aprender de punta a punta: electrónica y montaje del hardware, firmware de vuelo (ArduCopter), inteligencia artificial embebida y desarrollo de software — un servidor en Python/FastAPI y una app móvil en React Native.
El objetivo final es un quadcopter controlado por completo desde el teléfono: vídeo en vivo con detección de personas en tiempo real, telemetría y controles de vuelo (despegar, aterrizar, moverse, volver a casa, parada de emergencia), todo hablando con el Pixhawk vía MAVLink — sin mando de radio tradicional de por medio, aunque se sumará uno como respaldo.
Esta página resume la arquitectura, el estado real de cada pieza y algunos de los problemas más difíciles de depurar que me he encontrado por el camino.
Arquitectura
Hardware
- Controlador de vuelo Pixhawk 2.4.8 con firmware ArduCopter 4.6.3
- Raspberry Pi 4 Model B como compañero de a bordo (companion computer)
- Cámara Raspberry Pi AI (Sony IMX500) — inferencia de IA en el propio sensor
- 4× motores brushless A2212 1000KV + 4× ESC 30A (ICQUANZX) + hélices de 10"
- Batería LiPo 3S 5000mAh (11.1V nominal) — elegida para respetar la especificación del motor/ESC actual, pendiente de comprar
- PDB Matek PDB-XT60, con BEC integrado de 5V/1.5A — insuficiente por sí solo para alimentar la Raspberry Pi
- UBEC dedicado 5V/5A (entrada 2S-6S) + condensador de 220-470µF entre 5V y GND, para la Raspberry Pi — pendientes de comprar y montar
- Módulo de potencia 3DR, para que el Pixhawk mida voltaje y corriente de la batería
- GPS u-blox NEO-M9N — pendiente de conectar a la Pixhawk
- Pantalla OLED SSD1306 (previsiblemente por I2C) — pendiente de conectar
- Buzzer y botón/interruptor de seguridad
- Mando RadioMaster Zorro negro (ExpressLRS, EdgeTX) + receptor ELRS a juego (RadioMaster RP1/RP2 o Happymodel EP1/EP2) — pendientes de comprar
- Frame propio impreso en 3D a medida: patas, alas/brazos, tapa, base y separadores (piezas STL más abajo)
- Soporte de cámara impreso en 3D, diseñado para fijarla y descartar la vibración como causa de los recuadros de detección desencajados
- Cable de telemetría Pixhawk↔Raspberry Pi por el conector JST-GH del Pixhawk — rehecho a mano tras localizar un fallo del hilo de GND
- Extensiones de cable soldadas entre los ESC y los motores, porque los cables de fábrica no llegan
Software
- comando_server.py — servidor FastAPI en la Raspberry Pi que habla con el Pixhawk por MAVLink (dronekit/pymavlink): despegar, aterrizar, movimiento, RTL, parada de emergencia, telemetría y logs en vivo
- deteccion_personas_ai.py — captura vídeo con Picamera2 y corre un modelo SSD MobileNetV2 directamente en el chip IMX500 para detectar personas en tiempo real, dibuja los recuadros y publica el stream por RTSP/MediaMTX
- dronecam-app — app móvil propia (Expo / React Native) con vídeo en vivo, telemetría, consola de logs y controles de vuelo, hablando por WiFi local con la Raspberry Pi
Código en GitHub
Cómo funciona, de principio a fin
- La app móvil (dronecam-app) se conecta a la Raspberry Pi por WiFi local — sin pasar por internet ni por ningún servidor externo.
- La cámara IMX500 captura vídeo continuamente; cada fotograma se analiza en el propio chip de la cámara, que ya lleva la inferencia de IA integrada, así que la Raspberry Pi no tiene que cargar con ese trabajo.
- deteccion_personas_ai.py recoge esas detecciones, dibuja un recuadro verde sobre cada persona y envía el vídeo ya anotado a MediaMTX, que lo sirve como HLS hacia la app — todo con unos ~6 segundos de retraso.
- En paralelo, comando_server.py expone una API (FastAPI) con los controles de vuelo: despegar, aterrizar, moverse, volver a casa o parar de emergencia.
- Ese servidor traduce cada orden a MAVLink y se la manda al Pixhawk, que es quien de verdad controla los motores — la Raspberry Pi nunca vuela el dron directamente, solo le dice qué hacer.
- Un watchdog corta el vuelo y fuerza el regreso a casa si la app deja de responder más de 5 segundos, como red de seguridad.
Configuración de la Raspberry Pi
Sistema y arranque
- Raspberry Pi OS (64 bits) sobre una Raspberry Pi 4 Model B
- El zócalo de la tarjeta microSD tiene una avería física: hace contacto con la carcasa puesta (que presiona la tarjeta) pero lo pierde sin ella, que es como iría montada en el dron
- Solución en marcha: arrancar desde un USB en vez de desde la microSD, para evitar el zócalo roto del todo
sudo apt update && sudo apt upgrade -y
# permisos para acceder a la cámara y al puerto serie sin sudo
sudo usermod -a -G video,dialout $USER
sudo reboot
Telemetría con el Pixhawk
- Conectada al puerto TELEM2 del Pixhawk mediante el UART de hardware de la Raspberry Pi
- 921600 baudios en ambos extremos (antes estaban mal puestos a 115200)
- UART habilitado (enable_uart=1) y el Bluetooth integrado desactivado (dtoverlay=disable-bt) para liberar el UART de hardware completo, sin compartirlo con la consola serie
- Conexión verificada con un heartbeat MAVLink real y estable, leído con pymavlink
- La Raspberry Pi estuvo primero en TELEM1; se movió a TELEM2 para dejar TELEM1 libre y preparado para el futuro receptor de radio ELRS
# /boot/config.txt — al final del archivo
dtoverlay=disable-bt
enable_uart=1
# /boot/cmdline.txt — quitar "console=serial0,115200"
sudo systemctl disable bluetooth
sudo reboot
# tras reiniciar, comprobar que el puerto existe:
ls /dev/serial0
Servidor de comandos (comando_server.py)
- Desplegado en ~/Documents/_Codes/drone-env, sincronizado por git
- FastAPI + dronekit/pymavlink, escuchando en el puerto 8000
- Configuración en un archivo .env (copiado de .env.example) — sin él, el servidor se conectaba por defecto a un simulador (SITL) en vez de al Pixhawk real
- Watchdog interno: si no recibe un /ping de la app en 5 segundos, fuerza la vuelta a casa como red de seguridad
- Expone estos endpoints HTTP a la app: /ping, /despegar, /aterrizar, /rtl, /mover, /parar_movimiento, /parada_emergencia, /motor_test/iniciar, /motor_test/detener, /ejecutar_script, /detener_script, /logs y /telemetria
- Pendiente: montarlo también como servicio systemd para que arranque solo tras un reinicio
cd ~/Documents/_Codes/drone-env
pip install fastapi uvicorn dronekit pymavlink python-dotenv
# .env (copiado de .env.example):
DRONE_CONN=/dev/serial0:921600
DRONE_WATCHDOG_TIMEOUT=5
python comando_server.py # arranca en 0.0.0.0:8000
# comprobación rápida desde otra máquina de la misma red:
curl -X POST http://<ip-rpi>:8000/ping
Cámara y detección de personas
- deteccion_personas_ai.py corre la inferencia de IA directamente en el chip Sony IMX500 de la cámara, sin cargar la CPU de la Raspberry Pi
- Publica el vídeo por ffmpeg → RTSP (rtsp://localhost:8554/drone) → MediaMTX → HLS hacia la app
- Resolución 640×480 a 10 fps, con un buffer de solo 2 fotogramas en la cámara — antes eran 12, y esa cola es la que causaba el minuto de retraso (se iba llenando de fotogramas cada vez más viejos)
- Umbral de confianza de la detección: 0.6 (solo se dibuja la persona si el modelo supera esa confianza)
- Intervalo de keyframe de ffmpeg fijado a 1 segundo (-g, -keyint_min y -sc_threshold en 0) para mantener la latencia en vivo baja
sudo apt install -y rpicam-apps python3-picamera2 imx500-all
rpicam-hello --timeout 0 # comprobar que la cámara responde
# /boot/firmware/config.txt
camera_auto_detect=1
# modelo IA que usa deteccion_personas_ai.py:
# /usr/share/imx500-models/imx500_network_ssd_mobilenetv2_fpnlite_320x320_pp.rpk
# comando ffmpeg real que lanza el propio script (fotogramas por stdin -> RTSP):
ffmpeg -y -hide_banner -loglevel warning -nostats \
-use_wallclock_as_timestamps 1 \
-f rawvideo -pixel_format bgr24 -video_size 640x480 -framerate 10 \
-i - \
-c:v libx264 -preset ultrafast -tune zerolatency \
-g 10 -keyint_min 10 -sc_threshold 0 \
-pix_fmt yuv420p \
-f rtsp rtsp://localhost:8554/drone
Servicios systemd (arrancan solos con la Raspberry Pi)
- mediamtx.service — el servidor MediaMTX que recibe el RTSP y lo sirve como HLS a la app
- drone-camera.service — corre deteccion_personas_ai.py, que a su vez lanza ffmpeg; depende de mediamtx.service (After / Requires) para no arrancar antes de tiempo
- Restart=on-failure: si el proceso se cae, systemd lo reinicia solo a los 2 segundos
# /etc/systemd/system/drone-camera.service
[Unit]
Description=Streaming de camara IA (deteccion de personas) hacia MediaMTX
After=mediamtx.service
Requires=mediamtx.service
[Service]
WorkingDirectory=/home/emiki/Documents/_Codes/drone-env
ExecStart=/home/emiki/venv-ardupilot/bin/python3 /home/emiki/Documents/_Codes/drone-env/deteccion_personas_ai.py
Restart=on-failure
RestartSec=2
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now mediamtx.service drone-camera.service
# comprobar que van bien / ver sus logs:
sudo systemctl status drone-camera.service mediamtx.service
sudo journalctl -u drone-camera.service -f
Despliegue y actualización del código
- El código de la Raspberry Pi vive en el mismo repositorio que los dos PCs del usuario: nada de copiar archivos sueltos a mano
- Cada cambio se sube desde el PC y se actualiza en la Raspberry Pi con un git pull, así no hay riesgo de que una copia se quede desactualizada
cd ~/Documents/_Codes/drone-env
git pull
# si se tocó comando_server.py, reiniciarlo a mano (aún no es systemd):
# Ctrl+C en su terminal y volver a lanzar:
python comando_server.py
# si se tocó deteccion_personas_ai.py, el servicio se reinicia solo:
sudo systemctl restart drone-camera.service
Red y acceso desde la app
- Todo por WiFi local — sin pasar por internet ni por ningún servidor externo
- La IP de la Raspberry Pi cambia según la casa (Barcelona o Segur); la app móvil centraliza esa IP en un único archivo de configuración para no tocar el código cada vez
Retos técnicos que me ha costado resolver
Al migrar el streaming para que pasara por el chip de IA (de rpicam-vid a deteccion_personas_ai.py), el retraso del vídeo en la app pasó de unos segundos a ¡2-3 minutos!
Causa: Descarté primero el pipeline de vídeo y la propia app — reproduciendo el mismo RTSP en VLC tenía el mismo retraso, así que el problema estaba antes. La causa real: ffmpeg no fijaba el intervalo de keyframe, así que usaba uno por defecto de unos 25 segundos. MediaMTX necesita un keyframe para poder cerrar cada fragmento de vídeo (HLS), así que se veía obligado a esperar minutos antes de poder servir nada «en vivo».
Solución: Forzando un keyframe cada segundo (con -g, -keyint_min y -sc_threshold en ffmpeg) el retraso bajó de golpe a unos 6 segundos.
Tras limpiar la detección para que solo marcara personas, el recuadro verde salía casi siempre estirado, cubriendo prácticamente todo el vídeo en vez de encajar sobre la persona.
Causa: Eran dos bugs encadenados: primero, estaba reescalando las coordenadas a mano sin tener en cuenta que el modelo de IA razona sobre un cuadrado de 320×320 píxeles, no sobre la resolución real de la cámara. Segundo, incluso usando la función oficial de conversión de coordenadas de picamera2, esta espera las coordenadas ya normalizadas entre 0 y 1, y el modelo las estaba devolviendo en píxeles de su propia entrada.
Solución: Apliqué la misma normalización que usa el ejemplo oficial de picamera2/IMX500 antes de convertir las coordenadas, y el recuadro empezó a encajar de verdad sobre la persona. (De propina: un intento de arreglo a medio camino intentó modificar una propiedad de solo lectura de la librería y tumbó el servicio en un bucle de reinicios — se vio enseguida en los logs de systemd y se revirtió al momento.)
La Raspberry Pi no conseguía establecer un heartbeat MAVLink estable con el Pixhawk por el puerto serie — cero bytes, o bytes corruptos.
Causa: Descarté uno a uno: puertos alternativos, baudios, configuración UART de la Raspberry Pi, orientación de los cables TX/RX, e incluso el propio hardware UART con pruebas de loopback. Al final, la causa era puramente física: el cable de GND se había salido de su cavidad en el conector JST-GH del Pixhawk.
Solución: Un cable nuevo, bien montado, y corregir además unos baudios mal configurados (estaban a 115200 en vez de 921600) resolvieron el bloqueo en una sola tarde.
Estado del proyecto
Hecho
- Descartadas primero las causas de software: reinicios, configuración UART, orientación de los cables TX/RX y el propio hardware UART (probado con loopback)
- Localizada la causa real: el cable de GND se había salido de su cavidad en el conector JST-GH
- Cable nuevo montado y probado
- Corregidos los baudios, que estaban mal a 115200 en vez de 921600
- Heartbeat MAVLink confirmado de forma estable con pymavlink
- Movido el puerto de la Raspberry Pi a TELEM2, dejando TELEM1 preparado para el futuro receptor de radio
- Confirmado que el servidor de comandos lleva días funcionando sin problemas contra la Pixhawk real
Pendiente
Nada — cerrado.
Hecho
- Diagnosticada y resuelta una actualización automática de Expo Go que rompió la compatibilidad con el proyecto
- Revisado el código: ya implementa todos los controles (despegar, mover, motor test, telemetría, logs, scripts) contra los endpoints reales
- Creado un archivo de configuración central para la IP de la Raspberry Pi, que cambia según la casa
- Confirmada la conexión real app↔servidor: vídeo de la cámara visible y botones llegando a la Raspberry Pi
- Añadido el control de altitud (subir/bajar) al mando en pantalla
- Consola de logs ordenada (más reciente arriba) y limitada a las últimas 10 líneas
- Anotada la IP de la Raspberry Pi para cuando esté en Barcelona
Pendiente
- Panel nuevo de prueba manual de motores (botones ±10% por motor y para todos) — pendiente de probar contra la Pixhawk real
Hecho
- Pipeline completo construido y verificado en producción
- Montado como servicios systemd persistentes tras reinicio
- Migrado el streaming para incluir la detección de personas en el propio chip IMX500
- Diagnosticado y corregido un bug grave de latencia (2-3 minutos) causado por un intervalo de keyframe de ffmpeg sin fijar
- Confirmada la bajada de latencia a unos 6 segundos (a veces 3s) tras el arreglo
Pendiente
- Decidir si bajar aún más el keyframe, aceptar los ~6s actuales, o migrar a WebRTC más adelante
Hecho
- Desplegado en la Raspberry Pi y conectado de verdad a la Pixhawk real
- Implementados los endpoints de despegar, aterrizar, RTL, mover (con control de altitud), parada de emergencia, motor test, telemetría y logs en vivo
- Añadido un watchdog que fuerza la vuelta a casa si no recibe señal de la app en 5 segundos
- Mejorado el registro: ahora también quedan en el log las órdenes normales de vuelo, no solo los scripts lanzados
- Confirmado que los botones de subir/bajar velocidad de la app usan una velocidad vertical fija (0.5 m/s), sin ajuste posible
- Aclarado que las pruebas de motores en banco deben hacerse con los endpoints de motor test, no con los controles normales de mover, para evitar problemas de control
Pendiente
- Montarlo como servicio systemd para que persista tras reinicios
- Los avisos PreArm actuales (por falta de mando y receptor) se resolverán solos en cuanto esa pieza esté comprada
- Prueba manual de motores por pasos (botones ±10%, por motor o para todos) ya programada — pendiente de subir a la Raspberry Pi y probar con hardware real
Hecho
- Patas de aterrizaje — validadas
- Alas / brazos — validados
- Tapa superior — validada
- Base — validada
- Separadores — validados
Pendiente
- Comprobar cómo encajará el soporte del GPS
Hecho
- Identificado que el BEC del PDB no da abasto para la Raspberry Pi
- Decidida la arquitectura: UBEC dedicado más condensador
- Resuelto el conflicto de voltaje entre batería y motor/ESC, bajando de 4S a 3S para respetar la especificación del ESC actual
Pendiente
- Comprar la batería 3S 5000mAh, el UBEC 5V/5A (2S–6S) y el condensador de 220–470µF
- Montar el UBEC y probarlo en banco antes de volar (ver Ensamblaje)
Hecho
- Subido el umbral de confianza para reducir falsos positivos
- Quitado el dibujo de recuadros para objetos que no son personas
- Corregido el escalado del recuadro con la función oficial de conversión de coordenadas de picamera2
- Corregida la normalización de las coordenadas antes de esa conversión
- Confirmado que el recuadro ya encaja bien sobre la persona en la mayoría de los casos
- Diseñada una pieza de soporte para montar la cámara fija y aislar si lo que queda es vibración o el movimiento de sujetarla a mano
Pendiente
- Imprimir el soporte de cámara
- Probar con la cámara fija sobre una mesa, en un espacio más abierto, y confirmar si eso resuelve la inconsistencia que queda
Hecho
- Elegido el mando: RadioMaster Zorro negro, con protocolo ExpressLRS (ELRS)
- Dejado preparado el puerto de la Pixhawk donde irá conectado el receptor
Pendiente
- Comprar el mando y un receptor ELRS a juego
- Actualizar ambos a la misma versión de firmware antes de emparejarlos
- Emparejarlos (bind)
- Cablear y soldar el receptor a la Pixhawk
- Configurar el failsafe del receptor en modo «sin pulsos»
- Calibrar la radio y asignar los canales de vuelo
- Configurar el failsafe de pérdida de señal en ArduCopter
- Comprobar que el aviso de «receptor no encontrado» desaparece y probar a armar sin hélices
Hecho
- Diagnosticado el síntoma exacto: la tarjeta funciona con la carcasa puesta, pero pierde contacto sin ella
Pendiente
- Decidir entre: arrancar por USB (evita el zócalo roto del todo), un apaño mecánico con espuma o cinta (rápido pero poco fiable con las vibraciones de vuelo), o cambiar la Raspberry Pi por otra
- Trabajo en pausa hasta tomar esa decisión
Hecho
Nada todavía.
Pendiente
- Soldar el PDB
- Hacer extensiones a los cables de los ESC para que lleguen a los motores
- Conectar el GPS y la pantalla OLED a la Pixhawk / Raspberry Pi
- Fijar los ESCs a la tapa del frame
- Montar en la tapa el buzzer, el PDB, la cámara y el OLED
- Montar el UBEC y probar en banco (motores al 15%) que la Raspberry Pi no se resetea, antes de volar
Piezas del frame (impresión 3D)
El frame es un diseño propio, dibujado a medida e iterado más de una decena de veces hasta que todos los componentes encajaban bien. Estos son los STL de las piezas actuales, listos para imprimir:
Sobre qué sigo decidiendo
- Latencia de streaming: ¿quedarme en los ~6s actuales, apretar más, o migrar a WebRTC más adelante?
- Dónde ubicar la batería y el módulo de potencia 3DR dentro del frame
- Marca/modelo exactos de algunos componentes, pendientes de anotar
Por qué me embarqué en esto
Llevo más de 25 años como desarrollador, pero este proyecto me saca de mi zona de confort de una forma muy sana: aquí un bug no se queda solo en el código, puede estar en una soldadura floja, en un cable mal encajado, o en cómo un chip de IA normaliza sus coordenadas. Me obliga a razonar por capas — hardware, firmware, backend y app — y esa mezcla de electrónica, IA embebida y desarrollo de software es exactamente lo que más disfruto aprendiendo.