Miguel Quesada Martínez
Miguel Quesada Martínez
Software Engineer / Backend - Frontend

Cover Letter

I am a senior FullStack developer with over 25 years of experience, passionate about technology and working with people. Throughout my career I have developed solutions in multiple languages and environments (PHP, C#, JavaScript), managed databases such as SQL Server, MySQL and PostgreSQL, and administered Linux and Windows servers.

My career also includes technical management and project leadership in Spain and Brazil, which has given me a broad, mature and adaptable view of the software world.

I stand out for my way of working: I solve complex problems calmly, especially in critical situations, and I always keep a focus on clean architecture, code efficiency and performance. I integrate easily into teams, collaborate, listen, help and maintain a positive attitude even in difficult moments.

In addition to my technical skills, I bring a human dimension that I value as much as code: responsibility, empathy, good communication and the ability to build trust and a good environment. I speak Spanish, Catalan, English and Portuguese fluently, which allows me to collaborate naturally in multicultural settings.

In short: I am a senior professional with values, a human approach and a collaborative mindset, someone with real experience and who is easy and pleasant to work with.

Location: Barcelona, Spain · Contact

Languages

Spanish
Native
Catalan
Native
English
Fluent
Portuguese
Fluent
Download Resume (PDF)

Experience

  • Senior Fullstack Developer — Biogyne Spain
    02/2023 - 10/2025
    Full development of web systems, including an e-commerce for telecom operators with management and automation of internal processes for products, commercial conditions and customers.
    • E-commerce for telecom operators
    • Management and automation of internal processes
    PHP Laravel Vue.js Bootstrap Node.js
    Databases: SQL Server, MariaDB
    Version control: GIT
  • CTO and Senior Developer — Denusa (Destilaria Nova União S/A - Brazil)
    07/2013 - 12/2021
    Managed the IT department and provided technical leadership for corporate projects. Developed web applications and administered Linux/Windows infrastructure. Maintained the TOTVS ERP.
    • IT department management and leadership
    • Maintenance of TOTVS ERP
    PHP C# Laravel Zend Framework Bootstrap Vue.js
    Databases: MySQL, SQL Server
    Version control: GIT
  • Senior Developer — Segundamano.es
    01/2009 - 04/2013
    Responsible for the backend/fraud development team; programming and bug fixing.
    • Responsible for backend and fraud
    PHP C C++ Perl Bash JavaScript C#
    Databases: PostgreSQL, Oracle, SQL Server, MySQL
    Version control: SVN
  • Head of IT — Plastificados de Terrassa
    07/2006 - 12/2008
    Led the IT area and implemented internal management solutions and system migrations.
    PHP C#
    Databases: MySQL, SQL Server, Oracle, Access
  • Family Business / Developer — NELOSA
    03/1993 - 12/2006
    Developed management systems and e-commerce; maintained servers and databases; built an e-commerce from scratch.
    PHP Visual Basic
    Databases: MySQL, Access
Download Resume (PDF)

About Me

Education

  • Bachelor's Degree in Computer Engineering
    Cibernos — 1999 - 2002

Skills

PHP C# C C++ Python Node.js Bash Perl JavaScript Laravel Vue.js Bootstrap SQL Server MySQL PostgreSQL Git SVN Linux Windows Server AWS CD/CI Teamwork Quick learner Punctuality and responsibility Commitment Empathy Flexibility and adaptability Communication Problem solving Proactive Work capacity Motivation Leadership Creativity Social skills Reasoning ability Attention to detail

Languages

Spanish
Native
Catalan
Native
English
Fluent
Portuguese
Fluent

Contact

Email: miguel.quesada.martinez.1975@gmail.com

Phone: +34 677 252 313 / +55 64 99319 2296

Location: Barcelona, Spain

GitHub: miguelquesadamartinez

Download Resume (PDF)
Personal project

Autonomous drone with AI person detection

A quadcopter built from scratch: a Pixhawk flight controller running ArduCopter, a Raspberry Pi 4 as the onboard companion computer, an AI camera (Sony IMX500) that detects people on its own chip, live video and full flight control from a custom mobile app — all over local WiFi, with no traditional radio transmitter and no cloud services.

Pixhawk + ArduCopter 4.6.3 Raspberry Pi 4 Sony IMX500 AI camera FastAPI + MAVLink Custom Expo / React Native app Custom 3D-printed frame

A personal project to learn end to end: hardware electronics and assembly, flight firmware (ArduCopter), embedded AI, and software development — a Python/FastAPI server and a React Native mobile app.

The end goal is a quadcopter fully controlled from the phone: live video with real-time person detection, telemetry, and flight controls (takeoff, land, move, return-to-home, emergency stop), all talking to the Pixhawk over MAVLink — no traditional radio transmitter in the loop, though one will be added as a backup.

This page walks through the architecture, the real status of every piece, and a few of the hardest bugs I've had to track down along the way.

Architecture

Hardware

  • Pixhawk 2.4.8 flight controller running ArduCopter 4.6.3 firmware
  • Raspberry Pi 4 Model B as the onboard companion computer
  • Raspberry Pi AI Camera (Sony IMX500) — AI inference on the sensor itself
  • 4× A2212 1000KV brushless motors + 4× 30A ESCs (ICQUANZX) + 10" propellers
  • 3S 5000mAh LiPo battery (11.1V nominal) — chosen to stay within the current motor/ESC spec, still to be bought
  • Matek PDB-XT60 power distribution board, with a built-in 5V/1.5A BEC — not enough on its own to power the Raspberry Pi
  • Dedicated 5V/5A UBEC (2S-6S input) + a 220-470µF capacitor between 5V and GND, for the Raspberry Pi — still to be bought and fitted
  • 3DR power module, so the Pixhawk can measure battery voltage and current
  • u-blox NEO-M9N GPS — still to be wired to the Pixhawk
  • SSD1306 OLED display (likely over I2C) — still to be wired
  • Buzzer and safety switch
  • RadioMaster Zorro black transmitter (ExpressLRS, EdgeTX) + a matching ELRS receiver (RadioMaster RP1/RP2 or Happymodel EP1/EP2) — still to be bought
  • Custom-designed, 3D-printed frame: legs, arms, lid, base and spacers (STL files below)
  • 3D-printed camera mount, designed to rule out vibration as the cause of the detection box drifting off target
  • Telemetry cable between the Pixhawk and the Raspberry Pi through the Pixhawk's JST-GH connector — remade by hand after tracking down a broken GND wire
  • Soldered cable extensions between the ESCs and the motors, since the stock cables don't reach

Software

  • comando_server.py — a FastAPI server on the Raspberry Pi that talks to the Pixhawk over MAVLink (dronekit/pymavlink): takeoff, land, movement, RTL, emergency stop, live telemetry and logs
  • deteccion_personas_ai.py — captures video with Picamera2 and runs an SSD MobileNetV2 model directly on the IMX500 chip to detect people in real time, draws the bounding boxes and publishes the stream over RTSP/MediaMTX
  • dronecam-app — a custom mobile app (Expo / React Native) with live video, telemetry, a log console and flight controls, talking to the Raspberry Pi over local WiFi

Code on GitHub

How it works, end to end

From opening the app to the drone actually moving, several pieces talk to each other in real time:

  1. The mobile app (dronecam-app) connects to the Raspberry Pi over local WiFi — no internet, no external server involved.
  2. The IMX500 camera captures video continuously; each frame is analyzed on the camera's own chip, which already runs the AI inference, so the Raspberry Pi never has to carry that load.
  3. deteccion_personas_ai.py collects those detections, draws a green box around each person, and sends the annotated video to MediaMTX, which serves it as HLS to the app — all with about 6 seconds of delay.
  4. In parallel, comando_server.py exposes a FastAPI with the flight controls: takeoff, land, move, return-to-home or emergency stop.
  5. That server translates each command into MAVLink and sends it to the Pixhawk, which is the one actually flying the motors — the Raspberry Pi never flies the drone directly, it only tells it what to do.
  6. A watchdog cuts the flight short and forces a return-to-home if the app stops responding for more than 5 seconds, as a safety net.

Raspberry Pi setup

The Raspberry Pi is the onboard brain: it talks to the Pixhawk on one side and to the mobile app on the other. Here's how it's set up right now:

OS and boot

  • Raspberry Pi OS (64-bit) on a Raspberry Pi 4 Model B
  • The microSD card socket has a physical fault: it makes contact with the case on (which presses the card in place) but loses it without the case, which is how it will sit inside the drone
  • Fix in progress: boot from a USB drive instead of the microSD card, to avoid the broken socket entirely
sudo apt update && sudo apt upgrade -y

# permissions to access the camera and serial port without sudo
sudo usermod -a -G video,dialout $USER

sudo reboot

Telemetry with the Pixhawk

  • Connected to the Pixhawk's TELEM2 port through the Raspberry Pi's hardware UART
  • 921600 baud on both ends (it used to be wrongly set to 115200)
  • UART enabled (enable_uart=1) and the onboard Bluetooth disabled (dtoverlay=disable-bt) to free up the full hardware UART instead of sharing it with the serial console
  • Connection verified with a real, stable MAVLink heartbeat read via pymavlink
  • The Raspberry Pi was first on TELEM1; it was moved to TELEM2 to leave TELEM1 free and ready for the future ELRS radio receiver
# /boot/config.txt — at the end of the file
dtoverlay=disable-bt
enable_uart=1

# /boot/cmdline.txt — remove "console=serial0,115200"

sudo systemctl disable bluetooth
sudo reboot

# after rebooting, confirm the port exists:
ls /dev/serial0

Command server (comando_server.py)

  • Deployed at ~/Documents/_Codes/drone-env, kept in sync via git
  • FastAPI + dronekit/pymavlink, listening on port 8000
  • Configured through a .env file (copied from .env.example) — without it, the server defaulted to a simulator (SITL) instead of the real Pixhawk
  • Built-in watchdog: forces a return-to-home if it doesn't get a /ping from the app within 5 seconds, as a safety net
  • Exposes these HTTP endpoints to the app: /ping, /despegar, /aterrizar, /rtl, /mover, /parar_movimiento, /parada_emergencia, /motor_test/iniciar, /motor_test/detener, /ejecutar_script, /detener_script, /logs and /telemetria
  • Still to do: also set it up as a systemd service so it starts automatically after a reboot
cd ~/Documents/_Codes/drone-env
pip install fastapi uvicorn dronekit pymavlink python-dotenv

# .env (copied from .env.example):
DRONE_CONN=/dev/serial0:921600
DRONE_WATCHDOG_TIMEOUT=5

python comando_server.py   # starts on 0.0.0.0:8000

# quick check from another machine on the same network:
curl -X POST http://<rpi-ip>:8000/ping

Camera and person detection

  • deteccion_personas_ai.py runs AI inference directly on the camera's Sony IMX500 chip, with no load on the Raspberry Pi's CPU
  • Publishes video through ffmpeg → RTSP (rtsp://localhost:8554/drone) → MediaMTX → HLS to the app
  • 640×480 resolution at 10 fps, with a camera buffer of just 2 frames — it used to be 12, and that queue is what caused the one-minute delay (it kept filling up with older and older frames)
  • Detection confidence threshold: 0.6 (a person is only drawn if the model clears that confidence)
  • ffmpeg's keyframe interval fixed at 1 second (-g, -keyint_min and -sc_threshold set to 0) to keep live latency low
sudo apt install -y rpicam-apps python3-picamera2 imx500-all
rpicam-hello --timeout 0        # confirm the camera responds

# /boot/firmware/config.txt
camera_auto_detect=1

# AI model used by deteccion_personas_ai.py:
# /usr/share/imx500-models/imx500_network_ssd_mobilenetv2_fpnlite_320x320_pp.rpk

# the actual ffmpeg command the script launches (frames via 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

Systemd services (start automatically with the Raspberry Pi)

  • mediamtx.service — the MediaMTX server that receives the RTSP feed and serves it as HLS to the app
  • drone-camera.service — runs deteccion_personas_ai.py, which in turn launches ffmpeg; it depends on mediamtx.service (After / Requires) so it never starts too early
  • Restart=on-failure: if the process crashes, systemd restarts it after 2 seconds on its own
# /etc/systemd/system/drone-camera.service
[Unit]
Description=AI camera streaming (person detection) to 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

# check they're healthy / view their logs:
sudo systemctl status drone-camera.service mediamtx.service
sudo journalctl -u drone-camera.service -f

Deploying and updating the code

  • The Raspberry Pi's code lives in the same repository as the two PCs the user works from — no copying loose files around by hand
  • Every change is pushed from the PC and pulled onto the Raspberry Pi with a git pull, so there's no risk of a copy falling out of date
cd ~/Documents/_Codes/drone-env
git pull

# if comando_server.py changed, restart it by hand (not systemd yet):
# Ctrl+C in its terminal, then launch it again:
python comando_server.py

# if deteccion_personas_ai.py changed, the service restarts itself:
sudo systemctl restart drone-camera.service

Network and access from the app

  • Everything over local WiFi — no internet, no external server involved
  • The Raspberry Pi's IP changes depending on the house (Barcelona or Segur); the mobile app centralizes that IP in a single config file so the code never has to change

Technical challenges I've had to solve

Three of the most stubborn bugs on this project, and how I got to the bottom of each one:

Video delay spiked to 2-3 minutes

After moving streaming onto the AI chip (from rpicam-vid to deteccion_personas_ai.py), the video delay in the app jumped from a couple of seconds to 2-3 minutes.

Root cause: I first ruled out the video pipeline and the app itself — playing the same RTSP stream directly in VLC showed the same delay, so the problem was upstream. The real cause: ffmpeg wasn't setting a keyframe interval, so it fell back to a default of roughly 25 seconds. MediaMTX needs a keyframe to close each HLS segment, so it had to wait minutes before it could serve anything close to 'live'.

Fix: Forcing a keyframe every second (ffmpeg's -g, -keyint_min and -sc_threshold flags) dropped the delay straight down to about 6 seconds.

The detection box covered almost the whole screen

After cleaning up detection to only flag people, the green box almost always came out stretched, covering nearly the entire video instead of fitting around the person.

Root cause: It turned out to be two bugs stacked on top of each other: first, I was rescaling the coordinates by hand without accounting for the fact that the AI model reasons over a 320×320 square, not the camera's real resolution. Second, even using picamera2's official coordinate-conversion helper, it expects coordinates already normalized between 0 and 1, and the model was returning them in pixels of its own input instead.

Fix: Applying the same normalization used in the official picamera2/IMX500 example before converting the coordinates finally made the box fit the person properly. (Bonus bug: a mid-way fix attempt tried to reassign a read-only library property and crashed the service into a restart loop — spotted right away in the systemd logs and reverted immediately.)

No telemetry between the Pixhawk and the Raspberry Pi

The Raspberry Pi couldn't get a stable MAVLink heartbeat from the Pixhawk over the serial port — either zero bytes or corrupted ones.

Root cause: I ruled things out one by one: alternate ports, baud rates, the Raspberry Pi's UART configuration, TX/RX wire orientation, even the UART hardware itself with loopback tests. In the end the cause was purely physical: the GND wire had popped out of its cavity in the Pixhawk's JST-GH connector.

Fix: A new, properly seated cable, plus fixing a misconfigured baud rate (it was set to 115200 instead of 921600), solved the problem in a single afternoon.

Project status

Updated on September 23, 2026 — a living project that moves forward in spare time.

Pixhawk ↔ Raspberry Pi telemetry 100%
Solved: stable MAVLink heartbeat
Done
  • Ruled out software causes first: reboots, UART configuration, TX/RX wire orientation, and the UART hardware itself (tested with loopback)
  • Found the real cause: the GND wire had popped out of its cavity in the JST-GH connector
  • New cable seated and tested
  • Fixed the baud rate, which was wrongly set to 115200 instead of 921600
  • Stable MAVLink heartbeat confirmed with pymavlink
  • Moved the Raspberry Pi's port to TELEM2, leaving TELEM1 ready for the future RC receiver
  • Confirmed the command server has been running for days against the real Pixhawk without issues
Still to do

Nothing — closed.

Mobile app 100%
Video, telemetry and flight controls working
Done
  • Diagnosed and fixed an automatic Expo Go update that broke compatibility with the project
  • Reviewed the code: it already implements every control (takeoff, move, motor test, telemetry, logs, scripts) against the real endpoints
  • Created a central config file for the Raspberry Pi's IP, which changes depending on the house
  • Confirmed the real app↔server connection: camera video visible and button presses reaching the Raspberry Pi
  • Added altitude control (up/down) to the on-screen D-pad
  • Sorted the log console (newest first) and capped it to the last 10 lines
  • Noted down the Raspberry Pi's IP for when it's set up in Barcelona
Still to do
  • New manual motor test panel (±10% buttons per motor and for all of them) — pending testing against the real Pixhawk
Video streaming 90%
Live video with person detection, ~6s latency
Done
  • Full pipeline built and verified in production
  • Set up as persistent systemd services that survive reboots
  • Migrated streaming to include person detection on the IMX500 chip itself
  • Diagnosed and fixed a serious latency bug (2-3 minutes) caused by an unfixed ffmpeg keyframe interval
  • Confirmed latency dropped to about 6 seconds (sometimes 3s) after the fix
Still to do
  • Decide whether to lower the keyframe further, accept the current ~6s, or move to WebRTC later on
Command server 85%
Deployed and connected to the real Pixhawk
Done
  • Deployed on the Raspberry Pi and genuinely connected to the real Pixhawk
  • Implemented takeoff, land, RTL, move (with altitude control), emergency stop, motor test, telemetry and live log endpoints
  • Added a watchdog that forces a return-to-home if it doesn't hear from the app for 5 seconds
  • Improved logging so normal flight commands show up too, not just launched scripts
  • Confirmed the app's up/down speed buttons always send a fixed vertical speed (0.5 m/s), with no way to adjust it
  • Clarified that bench motor tests should use the dedicated motor-test endpoints, not the normal move controls, to avoid control issues
Still to do
  • Set it up as a systemd service so it survives reboots
  • The current PreArm warnings (missing transmitter and receiver) will resolve themselves once that part is bought
  • Manual step-by-step motor test (±10% buttons, per motor or all at once) already coded — pending deploy to the Raspberry Pi and testing on real hardware
Frame 83%
Parts printed and validated; GPS mount still to fit
Done
  • Landing legs — validated
  • Wings / arms — validated
  • Top cover — validated
  • Base — validated
  • Spacers — validated
Still to do
  • Check how the GPS mount will fit
Raspberry Pi power supply 80%
Architecture decided, still need to buy the UBEC and capacitor
Done
  • Identified that the PDB's built-in BEC can't keep up with the Raspberry Pi
  • Decided on the architecture: a dedicated UBEC plus a capacitor
  • Resolved the voltage conflict between the battery and the motor/ESC by dropping from 4S to 3S, to stay within the current ESC's spec
Still to do
  • Buy the 3S 5000mAh battery, the 5V/5A UBEC (2S–6S) and the 220–470µF capacitor
  • Mount the UBEC and bench-test it before flying (see Assembly)
Person detection (AI) 80%
Working; fine-tuning bounding-box stability
Done
  • Raised the confidence threshold to cut down false positives
  • Removed the bounding boxes for non-person objects
  • Fixed the box scaling using picamera2's official coordinate-conversion helper
  • Fixed the coordinate normalization that had to happen before that conversion
  • Confirmed the box now fits the person correctly most of the time
  • Designed a camera mount to test with a fixed camera and isolate whether what's left is vibration or hand movement
Still to do
  • Print the camera mount
  • Test with the camera fixed on a table, in a more open space, and confirm whether that fixes the remaining inconsistency
RC receiver and transmitter 25%
Chosen (RadioMaster Zorro + ELRS); still to be bought
Done
  • Chosen the transmitter: a black RadioMaster Zorro running ExpressLRS (ELRS)
  • Prepared the Pixhawk port the receiver will connect to
Still to do
  • Buy the transmitter and a matching ELRS receiver
  • Update both to the same firmware version before binding them
  • Bind them
  • Wire and solder the receiver to the Pixhawk
  • Set the receiver's failsafe to "no pulses"
  • Calibrate the radio and assign the flight channels
  • Configure ArduCopter's signal-loss failsafe
  • Confirm the "receiver not found" warning clears and try arming without propellers
Raspberry Pi microSD socket 25%
Hardware fault found; work paused
Done
  • Diagnosed the exact symptom: the card works with the case on, but loses contact without it
Still to do
  • Decide between: booting from USB (avoids the broken socket entirely), a mechanical fix with foam or tape (quick but unreliable with flight vibration), or swapping the Raspberry Pi for another one
  • Work is paused until that decision is made
Soldering, wiring and final assembly 0%
Final wiring and physical assembly still to do
Done

Nothing yet.

Still to do
  • Solder the PDB
  • Make extensions for the ESC-to-motor wires
  • Wire the GPS and OLED display to the Pixhawk / Raspberry Pi
  • Fix the ESCs to the frame's top cover
  • Mount the buzzer, PDB, camera and OLED on the top cover
  • Mount the UBEC and bench-test (motors at 15%) that the Raspberry Pi doesn't reset, before flying

Frame parts (3D printing)

The frame is a custom design, drawn from scratch and iterated through more than a dozen versions until every component fit. These are the STL files for the current parts, ready to print:

Download all (.zip)

Still deciding

  • Streaming latency: stay at the current ~6s, push it lower, or move to WebRTC later on?
  • Where to place the battery and the 3DR power module inside the frame
  • Exact brand/model of a few components, still to be noted down

Why I took this on

I've been a developer for over 25 years, but this project pushes me out of my comfort zone in a healthy way: here a bug doesn't just live in the code, it can be a loose solder joint, a badly seated cable, or how an AI chip normalizes its coordinates. It forces me to reason across layers — hardware, firmware, backend and app — and that mix of electronics, embedded AI and software development is exactly what I enjoy learning most.

Download Resume (PDF)